BuildSystem.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Collections.Specialized;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using Godot;
  11. using GodotTools.BuildLogger;
  12. using GodotTools.Utils;
  13. namespace GodotTools.Build
  14. {
  15. public static class BuildSystem
  16. {
  17. private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler,
  18. Action<string> stdErrHandler)
  19. {
  20. string dotnetPath = DotNetFinder.FindDotNetExe();
  21. if (dotnetPath == null)
  22. throw new FileNotFoundException("Cannot find the dotnet executable.");
  23. var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
  24. var startInfo = new ProcessStartInfo(dotnetPath);
  25. BuildArguments(buildInfo, startInfo.ArgumentList, editorSettings);
  26. string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
  27. stdOutHandler?.Invoke(launchMessage);
  28. if (Godot.OS.IsStdOutVerbose())
  29. Console.WriteLine(launchMessage);
  30. startInfo.RedirectStandardOutput = true;
  31. startInfo.RedirectStandardError = true;
  32. startInfo.UseShellExecute = false;
  33. startInfo.CreateNoWindow = true;
  34. startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"]
  35. = ((string)editorSettings.GetSetting("interface/editor/editor_language")).Replace('_', '-');
  36. // Needed when running from Developer Command Prompt for VS
  37. RemovePlatformVariable(startInfo.EnvironmentVariables);
  38. var process = new Process { StartInfo = startInfo };
  39. if (stdOutHandler != null)
  40. process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data);
  41. if (stdErrHandler != null)
  42. process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data);
  43. process.Start();
  44. process.BeginOutputReadLine();
  45. process.BeginErrorReadLine();
  46. return process;
  47. }
  48. public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
  49. {
  50. using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
  51. {
  52. process.WaitForExit();
  53. return process.ExitCode;
  54. }
  55. }
  56. public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler,
  57. Action<string> stdErrHandler)
  58. {
  59. using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
  60. {
  61. await process.WaitForExitAsync();
  62. return process.ExitCode;
  63. }
  64. }
  65. private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler,
  66. Action<string> stdErrHandler)
  67. {
  68. string dotnetPath = DotNetFinder.FindDotNetExe();
  69. if (dotnetPath == null)
  70. throw new FileNotFoundException("Cannot find the dotnet executable.");
  71. var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
  72. var startInfo = new ProcessStartInfo(dotnetPath);
  73. BuildPublishArguments(buildInfo, startInfo.ArgumentList, editorSettings);
  74. string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString();
  75. stdOutHandler?.Invoke(launchMessage);
  76. if (Godot.OS.IsStdOutVerbose())
  77. Console.WriteLine(launchMessage);
  78. startInfo.RedirectStandardOutput = true;
  79. startInfo.RedirectStandardError = true;
  80. startInfo.UseShellExecute = false;
  81. startInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"]
  82. = ((string)editorSettings.GetSetting("interface/editor/editor_language")).Replace('_', '-');
  83. // Needed when running from Developer Command Prompt for VS
  84. RemovePlatformVariable(startInfo.EnvironmentVariables);
  85. var process = new Process { StartInfo = startInfo };
  86. if (stdOutHandler != null)
  87. process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data);
  88. if (stdErrHandler != null)
  89. process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data);
  90. process.Start();
  91. process.BeginOutputReadLine();
  92. process.BeginErrorReadLine();
  93. return process;
  94. }
  95. public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
  96. {
  97. using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler))
  98. {
  99. process.WaitForExit();
  100. return process.ExitCode;
  101. }
  102. }
  103. private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments,
  104. EditorSettings editorSettings)
  105. {
  106. // `dotnet clean` / `dotnet build` commands
  107. arguments.Add(buildInfo.OnlyClean ? "clean" : "build");
  108. // Solution
  109. arguments.Add(buildInfo.Solution);
  110. // `dotnet clean` doesn't recognize these options
  111. if (!buildInfo.OnlyClean)
  112. {
  113. // Restore
  114. // `dotnet build` restores by default, unless requested not to
  115. if (!buildInfo.Restore)
  116. arguments.Add("--no-restore");
  117. // Incremental or rebuild
  118. if (buildInfo.Rebuild)
  119. arguments.Add("--no-incremental");
  120. }
  121. // Configuration
  122. arguments.Add("-c");
  123. arguments.Add(buildInfo.Configuration);
  124. // Verbosity
  125. AddVerbosityArguments(buildInfo, arguments, editorSettings);
  126. // Logger
  127. AddLoggerArgument(buildInfo, arguments);
  128. // Binary log
  129. AddBinaryLogArgument(buildInfo, arguments, editorSettings);
  130. // Custom properties
  131. foreach (var customProperty in buildInfo.CustomProperties)
  132. {
  133. arguments.Add("-p:" + (string)customProperty);
  134. }
  135. }
  136. private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments,
  137. EditorSettings editorSettings)
  138. {
  139. arguments.Add("publish"); // `dotnet publish` command
  140. // Solution
  141. arguments.Add(buildInfo.Solution);
  142. // Restore
  143. // `dotnet publish` restores by default, unless requested not to
  144. if (!buildInfo.Restore)
  145. arguments.Add("--no-restore");
  146. // Incremental or rebuild
  147. // TODO: Not supported in `dotnet publish` (https://github.com/dotnet/sdk/issues/11099)
  148. // if (buildInfo.Rebuild)
  149. // arguments.Add("--no-incremental");
  150. // Configuration
  151. arguments.Add("-c");
  152. arguments.Add(buildInfo.Configuration);
  153. // Runtime Identifier
  154. arguments.Add("-r");
  155. arguments.Add(buildInfo.RuntimeIdentifier!);
  156. // Self-published
  157. arguments.Add("--self-contained");
  158. arguments.Add("true");
  159. // Verbosity
  160. AddVerbosityArguments(buildInfo, arguments, editorSettings);
  161. // Logger
  162. AddLoggerArgument(buildInfo, arguments);
  163. // Binary log
  164. AddBinaryLogArgument(buildInfo, arguments, editorSettings);
  165. // Custom properties
  166. foreach (var customProperty in buildInfo.CustomProperties)
  167. {
  168. arguments.Add("-p:" + (string)customProperty);
  169. }
  170. // Publish output directory
  171. if (buildInfo.PublishOutputDir != null)
  172. {
  173. arguments.Add("-o");
  174. arguments.Add(buildInfo.PublishOutputDir);
  175. }
  176. }
  177. private static void AddVerbosityArguments(BuildInfo buildInfo, Collection<string> arguments,
  178. EditorSettings editorSettings)
  179. {
  180. var verbosityLevel =
  181. editorSettings.GetSetting(GodotSharpEditor.Settings.VerbosityLevel).As<VerbosityLevelId>();
  182. arguments.Add("-v");
  183. arguments.Add(verbosityLevel switch
  184. {
  185. VerbosityLevelId.Quiet => "quiet",
  186. VerbosityLevelId.Minimal => "minimal",
  187. VerbosityLevelId.Detailed => "detailed",
  188. VerbosityLevelId.Diagnostic => "diagnostic",
  189. _ => "normal",
  190. });
  191. if ((bool)editorSettings.GetSetting(GodotSharpEditor.Settings.NoConsoleLogging))
  192. arguments.Add("-noconlog");
  193. }
  194. private static void AddLoggerArgument(BuildInfo buildInfo, Collection<string> arguments)
  195. {
  196. string buildLoggerPath = Path.Combine(Internals.GodotSharpDirs.DataEditorToolsDir,
  197. "GodotTools.BuildLogger.dll");
  198. arguments.Add(
  199. $"-l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}");
  200. }
  201. private static void AddBinaryLogArgument(BuildInfo buildInfo, Collection<string> arguments,
  202. EditorSettings editorSettings)
  203. {
  204. if (!(bool)editorSettings.GetSetting(GodotSharpEditor.Settings.CreateBinaryLog))
  205. return;
  206. arguments.Add($"-bl:{Path.Combine(buildInfo.LogsDirPath, "msbuild.binlog")}");
  207. arguments.Add("-ds:False"); // Honestly never understood why -bl also switches -ds on.
  208. }
  209. private static void RemovePlatformVariable(StringDictionary environmentVariables)
  210. {
  211. // EnvironmentVariables is case sensitive? Seriously?
  212. var platformEnvironmentVariables = new List<string>();
  213. foreach (string env in environmentVariables.Keys)
  214. {
  215. if (env.ToUpperInvariant() == "PLATFORM")
  216. platformEnvironmentVariables.Add(env);
  217. }
  218. foreach (string env in platformEnvironmentVariables)
  219. environmentVariables.Remove(env);
  220. }
  221. }
  222. }