343 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			343 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.Xml;
 | 
						|
using Newtonsoft.Json;
 | 
						|
 | 
						|
namespace UnrealSharpBuildTool.Actions;
 | 
						|
 | 
						|
public class GenerateProject : BuildToolAction
 | 
						|
{
 | 
						|
    private string _projectPath = string.Empty;
 | 
						|
    private string _projectFolder = string.Empty;
 | 
						|
    private string _projectRoot = string.Empty;
 | 
						|
    
 | 
						|
    bool ContainsUPluginOrUProjectFile(string folder)
 | 
						|
    {
 | 
						|
        string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories);
 | 
						|
        
 | 
						|
        foreach (string file in files)
 | 
						|
        {
 | 
						|
            if (file.EndsWith(".uplugin", StringComparison.OrdinalIgnoreCase) || file.EndsWith(".uproject", StringComparison.OrdinalIgnoreCase))
 | 
						|
            {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    public override bool RunAction()
 | 
						|
    {
 | 
						|
        string folder = Program.TryGetArgument("NewProjectFolder");
 | 
						|
        _projectRoot = Program.TryGetArgument("ProjectRoot");
 | 
						|
        
 | 
						|
        if (!ContainsUPluginOrUProjectFile(_projectRoot))
 | 
						|
        {
 | 
						|
            throw new InvalidOperationException("Project folder must contain a .uplugin or .uproject file.");
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (folder == _projectRoot)
 | 
						|
        {
 | 
						|
            folder = Path.Combine(folder, "Script");
 | 
						|
        }
 | 
						|
 | 
						|
        string projectName = Program.TryGetArgument("NewProjectName");
 | 
						|
        string csProjFileName = $"{projectName}.csproj";
 | 
						|
 | 
						|
        if (!Directory.Exists(folder))
 | 
						|
        {
 | 
						|
            Directory.CreateDirectory(folder);
 | 
						|
        }
 | 
						|
 | 
						|
        _projectFolder = Path.Combine(folder, projectName);
 | 
						|
        _projectPath = Path.Combine(_projectFolder, csProjFileName);
 | 
						|
 | 
						|
        string version = Program.GetVersion();
 | 
						|
        using BuildToolProcess generateProjectProcess = new BuildToolProcess();
 | 
						|
 | 
						|
        // Create a class library.
 | 
						|
        generateProjectProcess.StartInfo.ArgumentList.Add("new");
 | 
						|
        generateProjectProcess.StartInfo.ArgumentList.Add("classlib");
 | 
						|
 | 
						|
        // Assign project name to the class library.
 | 
						|
        generateProjectProcess.StartInfo.ArgumentList.Add("-n");
 | 
						|
        generateProjectProcess.StartInfo.ArgumentList.Add(projectName);
 | 
						|
 | 
						|
        // Set the target framework to the current version.
 | 
						|
        generateProjectProcess.StartInfo.ArgumentList.Add("-f");
 | 
						|
        generateProjectProcess.StartInfo.ArgumentList.Add(version);
 | 
						|
 | 
						|
        generateProjectProcess.StartInfo.WorkingDirectory = folder;
 | 
						|
 | 
						|
        if (!generateProjectProcess.StartBuildToolProcess())
 | 
						|
        {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        // dotnet new class lib generates a file named Class1, remove it.
 | 
						|
        string myClassFile = Path.Combine(_projectFolder, "Class1.cs");
 | 
						|
        if (File.Exists(myClassFile))
 | 
						|
        {
 | 
						|
            File.Delete(myClassFile);
 | 
						|
        }
 | 
						|
 | 
						|
        string slnPath = Program.GetSolutionFile();
 | 
						|
        if (!File.Exists(slnPath))
 | 
						|
        {
 | 
						|
            GenerateSolution generateSolution = new GenerateSolution();
 | 
						|
            generateSolution.RunAction();
 | 
						|
        }
 | 
						|
 | 
						|
        if (Program.HasArgument("SkipUSharpProjSetup"))
 | 
						|
        {
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        AddLaunchSettings();
 | 
						|
        ModifyCSProjFile();
 | 
						|
 | 
						|
        string relativePath = Path.GetRelativePath(Program.GetScriptFolder(), _projectPath);
 | 
						|
        AddProjectToSln(relativePath);
 | 
						|
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    public static void AddProjectToSln(string relativePath)
 | 
						|
    {
 | 
						|
        AddProjectToSln([relativePath]);
 | 
						|
    }
 | 
						|
 | 
						|
    public static void AddProjectToSln(List<string> relativePaths)
 | 
						|
    {
 | 
						|
        foreach (IGrouping<string, string> projects in GroupPathsBySolutionFolder(relativePaths))
 | 
						|
        {
 | 
						|
            using BuildToolProcess addProjectToSln = new BuildToolProcess();
 | 
						|
            addProjectToSln.StartInfo.ArgumentList.Add("sln");
 | 
						|
            addProjectToSln.StartInfo.ArgumentList.Add("add");
 | 
						|
 | 
						|
            foreach (string relativePath in projects)
 | 
						|
            {
 | 
						|
                addProjectToSln.StartInfo.ArgumentList.Add(relativePath);
 | 
						|
            }
 | 
						|
 | 
						|
            addProjectToSln.StartInfo.ArgumentList.Add("-s");
 | 
						|
            addProjectToSln.StartInfo.ArgumentList.Add(projects.Key);
 | 
						|
 | 
						|
            addProjectToSln.StartInfo.WorkingDirectory = Program.GetScriptFolder();
 | 
						|
            addProjectToSln.StartBuildToolProcess();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private static IEnumerable<IGrouping<string, string>> GroupPathsBySolutionFolder(List<string> relativePaths)
 | 
						|
    {
 | 
						|
        return relativePaths.GroupBy(GetPathRelativeToProject)!;
 | 
						|
    }
 | 
						|
 | 
						|
    private static string GetPathRelativeToProject(string path)
 | 
						|
    {
 | 
						|
        var fullPath = Path.GetFullPath(path, Program.GetScriptFolder());
 | 
						|
        var relativePath = Path.GetRelativePath(Program.GetProjectDirectory(), fullPath);
 | 
						|
        var projectDirName = Path.GetDirectoryName(relativePath)!;
 | 
						|
 | 
						|
        // If we're in the script folder we want these to be in the Script solution folder, otherwise we want these to
 | 
						|
        // be in the directory for the plugin itself.
 | 
						|
        var containingDirName = Path.GetDirectoryName(projectDirName)!;
 | 
						|
        return containingDirName == "Script" ? containingDirName : Path.GetDirectoryName(containingDirName)!;
 | 
						|
    }
 | 
						|
 | 
						|
    private void ModifyCSProjFile()
 | 
						|
    {
 | 
						|
        try
 | 
						|
        {
 | 
						|
            XmlDocument csprojDocument = new XmlDocument();
 | 
						|
            csprojDocument.Load(_projectPath);
 | 
						|
 | 
						|
            if (csprojDocument.SelectSingleNode("//ItemGroup") is not XmlElement newItemGroup)
 | 
						|
            {
 | 
						|
                newItemGroup = csprojDocument.CreateElement("ItemGroup");
 | 
						|
                csprojDocument.DocumentElement!.AppendChild(newItemGroup);
 | 
						|
            }
 | 
						|
 | 
						|
            AppendProperties(csprojDocument);
 | 
						|
            AppendConstantDefines(csprojDocument);
 | 
						|
 | 
						|
            AppendReference(csprojDocument, newItemGroup, "UnrealSharp", GetPathToBinaries());
 | 
						|
            AppendReference(csprojDocument, newItemGroup, "UnrealSharp.Core", GetPathToBinaries());
 | 
						|
 | 
						|
            AppendSourceGeneratorReference(csprojDocument, newItemGroup);
 | 
						|
 | 
						|
            if (!Program.HasArgument("SkipIncludeProjectGlue"))
 | 
						|
            {
 | 
						|
                AppendGeneratedCode(csprojDocument, newItemGroup);
 | 
						|
            }
 | 
						|
 | 
						|
            foreach (string dependency in Program.GetArguments("Dependency"))
 | 
						|
            {
 | 
						|
                AddDependency(csprojDocument, newItemGroup, dependency);
 | 
						|
            }
 | 
						|
 | 
						|
            csprojDocument.Save(_projectPath);
 | 
						|
        }
 | 
						|
        catch (Exception ex)
 | 
						|
        {
 | 
						|
            throw new InvalidOperationException($"An error occurred while updating the .csproj file: {ex.Message}", ex);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    private void AddProperty(string name, string value, XmlDocument doc, XmlNode propertyGroup)
 | 
						|
    {
 | 
						|
        XmlNode? newProperty = propertyGroup.SelectSingleNode(name);
 | 
						|
 | 
						|
        if (newProperty == null)
 | 
						|
        {
 | 
						|
            newProperty = doc.CreateElement(name);
 | 
						|
            propertyGroup.AppendChild(newProperty);
 | 
						|
        }
 | 
						|
 | 
						|
        newProperty.InnerText = value;
 | 
						|
    }
 | 
						|
 | 
						|
    private void AppendProperties(XmlDocument doc)
 | 
						|
    {
 | 
						|
        XmlNode? propertyGroup = doc.SelectSingleNode("//PropertyGroup");
 | 
						|
 | 
						|
        if (propertyGroup == null)
 | 
						|
        {
 | 
						|
            propertyGroup = doc.CreateElement("PropertyGroup");
 | 
						|
        }
 | 
						|
 | 
						|
        AddProperty("CopyLocalLockFileAssembliesName", "true", doc, propertyGroup);
 | 
						|
        AddProperty("AllowUnsafeBlocks", "true", doc, propertyGroup);
 | 
						|
        AddProperty("EnableDynamicLoading", "true", doc, propertyGroup);
 | 
						|
    }
 | 
						|
    
 | 
						|
    private void AddConstDefine(string value, XmlDocument doc, XmlNode propertyGroup, string? condition = null)
 | 
						|
    {
 | 
						|
        var newProperty = doc.CreateElement("DefineConstants");
 | 
						|
        propertyGroup.AppendChild(newProperty);
 | 
						|
        newProperty.InnerText = value;
 | 
						|
        if (condition is not null)
 | 
						|
        {
 | 
						|
            newProperty.SetAttribute("Condition", condition);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private void AppendConstantDefines(XmlDocument doc)
 | 
						|
    {
 | 
						|
        var propertyGroup = doc.CreateElement("PropertyGroup");
 | 
						|
 | 
						|
        AddConstDefine("WITH_EDITOR", doc, propertyGroup);
 | 
						|
        AddConstDefine("$(DefineConstants.Replace('WITH_EDITOR;', '').Replace('WITH_EDITOR', ''))", doc, propertyGroup, 
 | 
						|
            "'$(DisableWithEditor)' == 'true'");
 | 
						|
        AddConstDefine("$(DefineConstants);$(DefineAdditionalConstants)", doc, propertyGroup, 
 | 
						|
            "'$(DefineAdditionalConstants)' != ''");
 | 
						|
    }
 | 
						|
 | 
						|
    private string GetPathToBinaries()
 | 
						|
    {
 | 
						|
        string directoryPath = Path.GetDirectoryName(_projectPath)!;
 | 
						|
        string unrealSharpPath = GetRelativePathToUnrealSharp(directoryPath);
 | 
						|
        return Path.Combine(unrealSharpPath, "Binaries", "Managed");
 | 
						|
    }
 | 
						|
 | 
						|
    private void AppendReference(XmlDocument doc, XmlElement itemGroup, string referenceName, string binPath)
 | 
						|
    {
 | 
						|
        XmlElement referenceElement = doc.CreateElement("Reference");
 | 
						|
        referenceElement.SetAttribute("Include", referenceName);
 | 
						|
 | 
						|
        XmlElement hintPath = doc.CreateElement("HintPath");
 | 
						|
        hintPath.InnerText = Path.Combine(binPath, Program.GetVersion(), referenceName + ".dll");
 | 
						|
        referenceElement.AppendChild(hintPath);
 | 
						|
        itemGroup.AppendChild(referenceElement);
 | 
						|
    }
 | 
						|
 | 
						|
    private void AppendSourceGeneratorReference(XmlDocument doc, XmlElement itemGroup)
 | 
						|
    {
 | 
						|
        string sourceGeneratorPath = Path.Combine(GetPathToBinaries(), "UnrealSharp.SourceGenerators.dll");
 | 
						|
        XmlElement sourceGeneratorReference = doc.CreateElement("Analyzer");
 | 
						|
        sourceGeneratorReference.SetAttribute("Include", sourceGeneratorPath);
 | 
						|
        itemGroup.AppendChild(sourceGeneratorReference);
 | 
						|
    }
 | 
						|
 | 
						|
    private void AppendPackageReference(XmlDocument doc, XmlElement itemGroup, string packageName, string packageVersion)
 | 
						|
    {
 | 
						|
        XmlElement packageReference = doc.CreateElement("PackageReference");
 | 
						|
        packageReference.SetAttribute("Include", packageName);
 | 
						|
        packageReference.SetAttribute("Version", packageVersion);
 | 
						|
        itemGroup.AppendChild(packageReference);
 | 
						|
    }
 | 
						|
 | 
						|
    private void AppendGeneratedCode(XmlDocument doc, XmlElement itemGroup)
 | 
						|
    {
 | 
						|
        string providedGlueName = Program.TryGetArgument("GlueProjectName");
 | 
						|
        string scriptFolder = string.IsNullOrEmpty(_projectRoot) ? Program.GetScriptFolder() : Path.Combine(_projectRoot, "Script");
 | 
						|
        string generatedGluePath = Path.Combine(scriptFolder, providedGlueName, $"{providedGlueName}.csproj");
 | 
						|
        AddDependency(doc, itemGroup, generatedGluePath);
 | 
						|
    }
 | 
						|
 | 
						|
    private void AddDependency(XmlDocument doc, XmlElement itemGroup, string dependency)
 | 
						|
    {
 | 
						|
        string relativePath = GetRelativePath(_projectFolder, dependency);
 | 
						|
 | 
						|
        XmlElement generatedCode = doc.CreateElement("ProjectReference");
 | 
						|
        generatedCode.SetAttribute("Include", relativePath);
 | 
						|
        itemGroup.AppendChild(generatedCode);
 | 
						|
    }
 | 
						|
 | 
						|
    private string GetRelativePathToUnrealSharp(string basePath)
 | 
						|
    {
 | 
						|
        string targetPath = Path.Combine(basePath, Program.BuildToolOptions.PluginDirectory);
 | 
						|
        return GetRelativePath(basePath, targetPath);
 | 
						|
    }
 | 
						|
 | 
						|
    public static string GetRelativePath(string basePath, string targetPath)
 | 
						|
    {
 | 
						|
        Uri baseUri = new Uri(basePath.EndsWith(Path.DirectorySeparatorChar.ToString())
 | 
						|
                ? basePath
 | 
						|
                : basePath + Path.DirectorySeparatorChar);
 | 
						|
        Uri targetUri = new Uri(targetPath);
 | 
						|
        Uri relativeUri = baseUri.MakeRelativeUri(targetUri);
 | 
						|
        return OperatingSystem.IsWindows() ? Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', '\\') : Uri.UnescapeDataString(relativeUri.ToString());
 | 
						|
    }
 | 
						|
 | 
						|
    void AddLaunchSettings()
 | 
						|
    {
 | 
						|
        string csProjectPath = Path.Combine(Program.GetScriptFolder(), _projectFolder);
 | 
						|
        string propertiesDirectoryPath = Path.Combine(csProjectPath, "Properties");
 | 
						|
        string launchSettingsPath = Path.Combine(propertiesDirectoryPath, "launchSettings.json");
 | 
						|
 | 
						|
        if (!Directory.Exists(propertiesDirectoryPath))
 | 
						|
        {
 | 
						|
            Directory.CreateDirectory(propertiesDirectoryPath);
 | 
						|
        }
 | 
						|
 | 
						|
        if (File.Exists(launchSettingsPath))
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        Program.CreateOrUpdateLaunchSettings(launchSettingsPath);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
public class Root
 | 
						|
{
 | 
						|
    [JsonProperty("profiles")]
 | 
						|
    public Profiles Profiles { get; set; } = new Profiles();
 | 
						|
}
 | 
						|
public class Profiles
 | 
						|
{
 | 
						|
    [JsonProperty("UnrealSharp")]
 | 
						|
    public Profile ProfileName { get; set; } = new Profile();
 | 
						|
}
 | 
						|
 | 
						|
public class Profile
 | 
						|
{
 | 
						|
    [JsonProperty("commandName")]
 | 
						|
    public string CommandName { get; set; } = string.Empty;
 | 
						|
 | 
						|
    [JsonProperty("executablePath")]
 | 
						|
    public string ExecutablePath { get; set; } = string.Empty;
 | 
						|
 | 
						|
    [JsonProperty("commandLineArgs")]
 | 
						|
    public string CommandLineArgs { get; set; } = string.Empty;
 | 
						|
}
 |