243 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.IO;
 | |
| using System.Linq;
 | |
| using System.Threading;
 | |
| using EpicGames.UHT.Types;
 | |
| 
 | |
| namespace UnrealSharpScriptGenerator.Utilities;
 | |
| 
 | |
| public readonly struct ProjectDirInfo
 | |
| {
 | |
|     private readonly string _projectName;
 | |
|     private readonly string _projectDirectory;
 | |
|     public HashSet<string>? Dependencies { get; }
 | |
| 
 | |
|     public ProjectDirInfo(string projectName, string projectDirectory, HashSet<string>? dependencies = null)
 | |
|     {
 | |
|         _projectName = projectName;
 | |
|         _projectDirectory = projectDirectory;
 | |
|         Dependencies = dependencies;
 | |
|     }
 | |
|     
 | |
|     public string GlueProjectName => $"{_projectName}.Glue";
 | |
|     public string GlueProjectName_LEGACY => $"{_projectName}.PluginGlue";
 | |
|     public string GlueProjectFile => $"{GlueProjectName}.csproj";
 | |
|     
 | |
|     public string ScriptDirectory => Path.Combine(_projectDirectory, "Script");
 | |
|     
 | |
|     public string GlueCsProjPath => Path.Combine(GlueProjectDirectory, GlueProjectFile);
 | |
| 
 | |
|     public bool IsUProject => _projectDirectory.EndsWith(".uproject", StringComparison.OrdinalIgnoreCase);
 | |
|     
 | |
|     public bool IsPartOfEngine => _projectName == "Engine";
 | |
|     
 | |
|     public string GlueProjectDirectory => Path.Combine(ScriptDirectory, GlueProjectName);
 | |
|     public string GlueProjectDirectory_LEGACY => Path.Combine(ScriptDirectory, GlueProjectName_LEGACY);
 | |
|     
 | |
|     public string ProjectRoot => _projectDirectory;
 | |
| }
 | |
| 
 | |
| public static class FileExporter
 | |
| {
 | |
|     private static readonly ReaderWriterLockSlim ReadWriteLock = new();
 | |
| 
 | |
|     private static readonly List<string> ChangedFiles = new();
 | |
|     private static readonly List<string> UnchangedFiles = new();
 | |
| 
 | |
|     public static void SaveGlueToDisk(UhtType type, GeneratorStringBuilder stringBuilder)
 | |
|     {
 | |
|         string directory = GetDirectoryPath(type.Package);
 | |
|         SaveGlueToDisk(type.Package, directory, type.EngineName, stringBuilder.ToString());
 | |
|     }
 | |
| 
 | |
|     public static string GetFilePath(string typeName, string directory)
 | |
|     {
 | |
|         return Path.Combine(directory, $"{typeName}.generated.cs");
 | |
|     }
 | |
| 
 | |
|     public static void SaveGlueToDisk(UhtPackage package, string directory, string typeName, string text)
 | |
|     {
 | |
|         string absoluteFilePath = GetFilePath(typeName, directory);
 | |
|         bool directoryExists = Directory.Exists(directory);
 | |
|         bool glueExists = File.Exists(absoluteFilePath);
 | |
| 
 | |
|         ReadWriteLock.EnterWriteLock();
 | |
|         try
 | |
|         {
 | |
|             bool matchingGlue = glueExists && File.ReadAllText(absoluteFilePath) == text;
 | |
| 
 | |
|             // If the directory exists and the file exists with the same text, we can return early
 | |
|             if (directoryExists && matchingGlue)
 | |
|             {
 | |
|                 UnchangedFiles.Add(absoluteFilePath);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (!directoryExists)
 | |
|             {
 | |
|                 Directory.CreateDirectory(directory);
 | |
|             }
 | |
| 
 | |
|             File.WriteAllText(absoluteFilePath, text);
 | |
|             ChangedFiles.Add(absoluteFilePath);
 | |
| 
 | |
|             if (package.IsPartOfEngine())
 | |
|             {
 | |
|                 CSharpExporter.HasModifiedEngineGlue = true;
 | |
|             }
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             ReadWriteLock.ExitWriteLock();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public static void AddUnchangedType(UhtType type)
 | |
|     {
 | |
|         string directory = GetDirectoryPath(type.Package);
 | |
|         string filePath = GetFilePath(type.EngineName, directory);
 | |
|         UnchangedFiles.Add(filePath);
 | |
| 
 | |
|         if (type is UhtStruct uhtStruct && uhtStruct.Functions.Any(f => f.HasMetadata("ExtensionMethod")))
 | |
|         {
 | |
|             UnchangedFiles.Add(GetFilePath($"{type.EngineName}_Extensions", directory));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public static string GetDirectoryPath(UhtPackage package)
 | |
|     {
 | |
|         if (package == null)
 | |
|         {
 | |
|             throw new InvalidOperationException("Package is null");
 | |
|         }
 | |
| 
 | |
|         string rootPath = GetGluePath(package);
 | |
|         return Path.Combine(rootPath, package.GetShortName());
 | |
|     }
 | |
| 
 | |
|     public static string GetGluePath(UhtPackage package)
 | |
|     {
 | |
|         ProjectDirInfo projectDirInfo = package.FindOrAddProjectInfo();
 | |
|         return projectDirInfo.GlueProjectDirectory;
 | |
|     }
 | |
| 
 | |
|     public static void CleanOldExportedFiles()
 | |
|     {
 | |
|         Console.WriteLine("Cleaning up old generated C# glue files...");
 | |
|         CleanFilesInDirectories(Program.EngineGluePath);
 | |
|         CleanFilesInDirectories(Program.ProjectGluePath_LEGACY, true);
 | |
|         
 | |
|         foreach (ProjectDirInfo pluginDirectory in Program.PluginDirs)
 | |
|         {
 | |
|             CleanFilesInDirectories(pluginDirectory.GlueProjectDirectory, true);
 | |
|             CleanFilesInDirectories(pluginDirectory.GlueProjectDirectory_LEGACY, true);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public static void CleanModuleFolders()
 | |
|     {
 | |
|         CleanGeneratedFolder(Program.EngineGluePath);
 | |
|         CleanGeneratedFolder(Program.ProjectGluePath_LEGACY);
 | |
|         
 | |
|         foreach (ProjectDirInfo pluginDirectory in Program.PluginDirs)
 | |
|         {
 | |
|             CleanGeneratedFolder(pluginDirectory.GlueProjectDirectory);
 | |
|             CleanGeneratedFolder(pluginDirectory.GlueProjectDirectory_LEGACY);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public static void CleanGeneratedFolder(string path)
 | |
|     {
 | |
|         if (!Directory.Exists(path))
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         HashSet<string> ignoredDirectories = GetIgnoredDirectories(path);
 | |
| 
 | |
|         // TODO: Move runtime glue to a separate csproj. So we can fully clean the ProjectGlue folder.
 | |
|         // Below is a temporary solution to not delete runtime glue that can cause compilation errors on editor startup,
 | |
|         // and avoid having to restore nuget packages.
 | |
|         string[] directories = Directory.GetDirectories(path);
 | |
|         foreach (string directory in directories)
 | |
|         {
 | |
|             if (IsIntermediateDirectory(directory) || ignoredDirectories.Contains(Path.GetRelativePath(path, directory)))
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             Directory.Delete(directory, true);
 | |
|         }
 | |
|     }
 | |
|     private static HashSet<string> GetIgnoredDirectories(string path)
 | |
|     {
 | |
|         string glueIgnoreFileName = Path.Combine(path, ".glueignore");
 | |
|         if (!File.Exists(glueIgnoreFileName))
 | |
|         {
 | |
|             return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 | |
|         }
 | |
| 
 | |
|         HashSet<string> ignoredDirectories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 | |
|         using StreamReader fileInput = File.OpenText(glueIgnoreFileName);
 | |
|         while (!fileInput.EndOfStream)
 | |
|         {
 | |
|             string? line = fileInput.ReadLine();
 | |
|             if (string.IsNullOrWhiteSpace(line)) continue;
 | |
| 
 | |
|             ignoredDirectories.Add(line.Trim());
 | |
|         }
 | |
|         return ignoredDirectories;
 | |
|     }
 | |
| 
 | |
|     private static void CleanFilesInDirectories(string path, bool recursive = false)
 | |
|     {
 | |
|         if (!Directory.Exists(path))
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         string[] directories = Directory.GetDirectories(path);
 | |
|         HashSet<string> ignoredDirectories = GetIgnoredDirectories(path);
 | |
| 
 | |
|         foreach (var directory in directories)
 | |
|         {
 | |
|             if (ignoredDirectories.Contains(Path.GetRelativePath(path, directory)))
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             string moduleName = Path.GetFileName(directory);
 | |
|             if (!CSharpExporter.HasBeenExported(moduleName))
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             int removedFiles = 0;
 | |
|             string[] files = Directory.GetFiles(directory);
 | |
| 
 | |
|             foreach (var file in files)
 | |
|             {
 | |
|                 if (ChangedFiles.Contains(file) || UnchangedFiles.Contains(file))
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 File.Delete(file);
 | |
|                 removedFiles++;
 | |
|             }
 | |
| 
 | |
|             if (removedFiles == files.Length)
 | |
|             {
 | |
|                 Directory.Delete(directory, recursive);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     static bool IsIntermediateDirectory(string path)
 | |
|     {
 | |
|         string directoryName = Path.GetFileName(path);
 | |
|         return directoryName is "obj" or "bin" or "Properties";
 | |
|     }
 | |
| }
 |