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