Files
BusyRabbit/Plugins/UnrealSharp/Source/UnrealSharpManagedGlue/Utilities/FileExporter.cs
wyatt 648386cd73 Lua向C#逻辑迁移 一期 #13
将整个插件代码上传
2025-10-26 21:48:39 +08:00

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