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