using System.Xml.Linq; using CommandLine; using Newtonsoft.Json; using UnrealSharpBuildTool.Actions; namespace UnrealSharpBuildTool; public static class Program { public static BuildToolOptions BuildToolOptions = null!; public static int Main(string[] args) { try { Console.WriteLine(">>> UnrealSharpBuildTool"); Parser parser = new Parser(with => with.HelpWriter = null); ParserResult result = parser.ParseArguments(args); if (result.Tag == ParserResultType.NotParsed) { BuildToolOptions.PrintHelp(result); string errors = string.Empty; foreach (Error error in result.Errors) { if (error is TokenError tokenError) { errors += $"{tokenError.Tag}: {tokenError.Token} \n"; } } throw new Exception($"Invalid arguments. Errors: {errors}"); } BuildToolOptions = result.Value; if (!BuildToolAction.InitializeAction()) { return 1; } Console.WriteLine($"UnrealSharpBuildTool executed {BuildToolOptions.Action.ToString()} action successfully."); } catch (Exception exception) { Console.WriteLine("An error occurred: " + exception.Message + Environment.NewLine + exception.StackTrace); return 1; } return 0; } public static string TryGetArgument(string argument) { return BuildToolOptions.TryGetArgument(argument); } public static IEnumerable GetArguments(string argument) { return BuildToolOptions.GetArguments(argument); } public static bool HasArgument(string argument) { return BuildToolOptions.HasArgument(argument); } public static string GetSolutionFile() { return Path.Combine(GetScriptFolder(), BuildToolOptions.ProjectName + ".sln"); } public static string GetUProjectFilePath() { return Path.Combine(BuildToolOptions.ProjectDirectory, BuildToolOptions.ProjectName + ".uproject"); } public static string GetBuildConfiguration() { string buildConfig = TryGetArgument("BuildConfig"); if (string.IsNullOrEmpty(buildConfig)) { buildConfig = "Debug"; } return buildConfig; } public static BuildConfig GetBuildConfig() { string buildConfig = GetBuildConfiguration(); Enum.TryParse(buildConfig, out BuildConfig config); return config; } public static string GetBuildConfiguration(BuildConfig buildConfig) { return buildConfig switch { BuildConfig.Debug => "Debug", BuildConfig.Release => "Release", BuildConfig.Publish => "Release", _ => "Release" }; } public static string GetScriptFolder() { return Path.Combine(BuildToolOptions.ProjectDirectory, "Script"); } public static string GetPluginsFolder() { return Path.Combine(BuildToolOptions.ProjectDirectory, "Plugins"); } public static string GetProjectDirectory() { return BuildToolOptions.ProjectDirectory; } public static string FixPath(string path) { if (OperatingSystem.IsWindows()) { return path.Replace('/', '\\'); } return path; } public static string GetProjectNameAsManaged() { return "Managed" + BuildToolOptions.ProjectName; } public static string GetOutputPath(string rootDir = "") { if (string.IsNullOrEmpty(rootDir)) { rootDir = BuildToolOptions.ProjectDirectory; } return Path.Combine(rootDir, "Binaries", "Managed"); } public static string GetWeaver() { return Path.Combine(GetManagedBinariesDirectory(), "UnrealSharpWeaver.dll"); } public static string GetManagedBinariesDirectory() { return Path.Combine(BuildToolOptions.PluginDirectory, "Binaries", "Managed"); } public static string GetVersion() { Version currentVersion = Environment.Version; string currentVersionStr = $"{currentVersion.Major}.{currentVersion.Minor}"; return "net" + currentVersionStr; } public static void CreateOrUpdateLaunchSettings(string launchSettingsPath) { Root root = new Root(); string executablePath = string.Empty; if (OperatingSystem.IsWindows()) { executablePath = Path.Combine(BuildToolOptions.EngineDirectory, "Binaries", "Win64", "UnrealEditor.exe"); } else if (OperatingSystem.IsMacOS()) { executablePath = Path.Combine(BuildToolOptions.EngineDirectory, "Binaries", "Mac", "UnrealEditor"); } string commandLineArgs = FixPath(GetUProjectFilePath()); // Create a new profile if it doesn't exist if (root.Profiles == null) { root.Profiles = new Profiles(); } root.Profiles.ProfileName = new Profile { CommandName = "Executable", ExecutablePath = executablePath, CommandLineArgs = $"\"{commandLineArgs}\"", }; string newJsonString = JsonConvert.SerializeObject(root, Formatting.Indented); StreamWriter writer = File.CreateText(launchSettingsPath); writer.Write(newJsonString); writer.Close(); } public static List GetAllProjectFiles(DirectoryInfo folder) { return folder.GetDirectories("Script") .SelectMany(GetProjectsInDirectory) .Concat(folder.GetDirectories("Plugins") .SelectMany(x => x.EnumerateFiles("*.uplugin", SearchOption.AllDirectories)) .Select(x => x.Directory) .Select(x => x!.GetDirectories("Script").FirstOrDefault()) .Where(x => x is not null) .SelectMany(GetProjectsInDirectory!)) .ToList(); } public static Dictionary> GetProjectFilesByDirectory(DirectoryInfo folder) { Dictionary> result = new Dictionary>(); DirectoryInfo? scriptsFolder = folder.GetDirectories("Script").FirstOrDefault(); if (scriptsFolder is not null) { result.Add(GetOutputPathForDirectory(scriptsFolder), GetProjectsInDirectory(scriptsFolder).ToList()); } foreach (DirectoryInfo? pluginFolder in folder.GetDirectories("Plugins") .SelectMany(x => x.EnumerateFiles("*.uplugin", SearchOption.AllDirectories)) .Select(x => x.Directory) .Select(x => x!.GetDirectories("Script").FirstOrDefault()) .Where(x => x is not null)) { result.Add(GetOutputPathForDirectory(pluginFolder!), GetProjectsInDirectory(pluginFolder!).ToList()); } return result; } private static string GetOutputPathForDirectory(DirectoryInfo directory) { return Path.Combine(directory.Parent!.FullName, "Binaries", "Managed"); } private static IEnumerable GetProjectsInDirectory(DirectoryInfo folder) { var csprojFiles = folder.EnumerateFiles("*.csproj", SearchOption.AllDirectories); var fsprojFiles = folder.EnumerateFiles("*.fsproj", SearchOption.AllDirectories); return csprojFiles.Concat(fsprojFiles).Where(IsWeavableProject); } private static bool IsWeavableProject(FileInfo projectFile) { // We need to be able to filter out certain non-production projects. // The main target of this is source generators and analyzers which users // may want to leverage as part of their solution and can't be weaved because // they have to use netstandard2.0. XDocument doc = XDocument.Load(projectFile.FullName); return !doc.Descendants() .Where(element => element.Name.LocalName == "PropertyGroup") .SelectMany(element => element.Elements()) .Any(element => element.Name.LocalName == "ExcludeFromWeaver" && element.Value.Equals("true", StringComparison.OrdinalIgnoreCase)); } }