diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 5c8353b422ac..31a51ddc7acf 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -15,14 +15,14 @@ public class GodotBuildLogger : ILogger public void Initialize(IEventSource eventSource) { if (null == Parameters) - throw new LoggerException("Log directory was not set."); + throw new LoggerException("Log directory parameter not specified."); var parameters = Parameters.Split(new[] {';'}); string logDir = parameters[0]; if (string.IsNullOrEmpty(logDir)) - throw new LoggerException("Log directory was not set."); + throw new LoggerException("Log directory parameter is empty."); if (parameters.Length > 1) throw new LoggerException("Too many parameters passed."); @@ -51,22 +51,31 @@ public void Initialize(IEventSource eventSource) { throw new LoggerException("Failed to create log file: " + ex.Message); } - else - { - // Unexpected failure - throw; - } + + // Unexpected failure + throw; } eventSource.ProjectStarted += eventSource_ProjectStarted; - eventSource.TaskStarted += eventSource_TaskStarted; + eventSource.ProjectFinished += eventSource_ProjectFinished; eventSource.MessageRaised += eventSource_MessageRaised; eventSource.WarningRaised += eventSource_WarningRaised; eventSource.ErrorRaised += eventSource_ErrorRaised; - eventSource.ProjectFinished += eventSource_ProjectFinished; } - void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) + private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) + { + WriteLine(e.Message); + indent++; + } + + private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) + { + indent--; + WriteLine(e.Message); + } + + private void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) { string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}"; @@ -81,7 +90,7 @@ void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) issuesStreamWriter.WriteLine(errorLine); } - void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) + private void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) { string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}"; @@ -108,40 +117,6 @@ private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) } } - private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) - { - // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName - // To keep this log clean, this logger will ignore these events. - } - - private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) - { - WriteLine(e.Message); - indent++; - } - - private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) - { - indent--; - WriteLine(e.Message); - } - - /// - /// Write a line to the log, adding the SenderName - /// - private void WriteLineWithSender(string line, BuildEventArgs e) - { - if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line); - } - else - { - WriteLine(e.SenderName + ": " + line); - } - } - /// /// Write a line to the log, adding the SenderName and Message /// (these parameters are on all MSBuild event argument objects) diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs deleted file mode 100644 index 709cae70b7df..000000000000 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ /dev/null @@ -1,339 +0,0 @@ -using Godot; -using System; -using System.IO; -using Godot.Collections; -using GodotTools.Internals; -using static GodotTools.Internals.Globals; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; - -namespace GodotTools -{ - public class BottomPanel : VBoxContainer - { - private EditorInterface editorInterface; - - private TabContainer panelTabs; - - private VBoxContainer panelBuildsTab; - - private ItemList buildTabsList; - private TabContainer buildTabs; - - private ToolButton warningsBtn; - private ToolButton errorsBtn; - private Button viewLogBtn; - - private void _UpdateBuildTab(int index, int? currentTab) - { - var tab = (BuildTab)buildTabs.GetChild(index); - - string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution); - itemName += " [" + tab.BuildInfo.Configuration + "]"; - - buildTabsList.AddItem(itemName, tab.IconTexture); - - string itemTooltip = "Solution: " + tab.BuildInfo.Solution; - itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration; - itemTooltip += "\nStatus: "; - - if (tab.BuildExited) - itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored"; - else - itemTooltip += "Running"; - - if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error) - itemTooltip += $"\nErrors: {tab.ErrorCount}"; - - itemTooltip += $"\nWarnings: {tab.WarningCount}"; - - buildTabsList.SetItemTooltip(index, itemTooltip); - - // If this tab was already selected before the changes or if no tab was selected - if (currentTab == null || currentTab == index) - { - buildTabsList.Select(index); - _BuildTabsItemSelected(index); - } - } - - private void _UpdateBuildTabsList() - { - buildTabsList.Clear(); - - int? currentTab = buildTabs.CurrentTab; - - if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) - currentTab = null; - - for (int i = 0; i < buildTabs.GetChildCount(); i++) - _UpdateBuildTab(i, currentTab); - } - - public BuildTab GetBuildTabFor(BuildInfo buildInfo) - { - foreach (var buildTab in new Array(buildTabs.GetChildren())) - { - if (buildTab.BuildInfo.Equals(buildInfo)) - return buildTab; - } - - var newBuildTab = new BuildTab(buildInfo); - AddBuildTab(newBuildTab); - - return newBuildTab; - } - - private void _BuildTabsItemSelected(int idx) - { - if (idx < 0 || idx >= buildTabs.GetTabCount()) - throw new IndexOutOfRangeException(); - - buildTabs.CurrentTab = idx; - if (!buildTabs.Visible) - buildTabs.Visible = true; - - warningsBtn.Visible = true; - errorsBtn.Visible = true; - viewLogBtn.Visible = true; - } - - private void _BuildTabsNothingSelected() - { - if (buildTabs.GetTabCount() != 0) - { - // just in case - buildTabs.Visible = false; - - // This callback is called when clicking on the empty space of the list. - // ItemList won't deselect the items automatically, so we must do it ourselves. - buildTabsList.UnselectAll(); - } - - warningsBtn.Visible = false; - errorsBtn.Visible = false; - viewLogBtn.Visible = false; - } - - private void _WarningsToggled(bool pressed) - { - int currentTab = buildTabs.CurrentTab; - - if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) - throw new InvalidOperationException("No tab selected"); - - var buildTab = (BuildTab)buildTabs.GetChild(currentTab); - buildTab.WarningsVisible = pressed; - buildTab.UpdateIssuesList(); - } - - private void _ErrorsToggled(bool pressed) - { - int currentTab = buildTabs.CurrentTab; - - if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) - throw new InvalidOperationException("No tab selected"); - - var buildTab = (BuildTab)buildTabs.GetChild(currentTab); - buildTab.ErrorsVisible = pressed; - buildTab.UpdateIssuesList(); - } - - public void BuildProjectPressed() - { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) - return; // No solution to build - - string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); - string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); - - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); - - if (File.Exists(editorScriptsMetadataPath)) - { - try - { - File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - } - catch (IOException e) - { - GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}"); - return; - } - } - - bool buildSuccess = BuildManager.BuildProjectBlocking("Debug"); - - if (!buildSuccess) - return; - - // Notify running game for hot-reload - Internal.ScriptEditorDebuggerReloadScripts(); - - // Hot-reload in the editor - GodotSharpEditor.Instance.GetNode("HotReloadAssemblyWatcher").RestartTimer(); - - if (Internal.IsAssembliesReloadingNeeded()) - Internal.ReloadAssemblies(softReload: false); - } - - private void _ViewLogPressed() - { - if (!buildTabsList.IsAnythingSelected()) - return; - - var selectedItems = buildTabsList.GetSelectedItems(); - - if (selectedItems.Length != 1) - throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}"); - - int selectedItem = selectedItems[0]; - - var buildTab = (BuildTab)buildTabs.GetTabControl(selectedItem); - - OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName)); - } - - public override void _Notification(int what) - { - base._Notification(what); - - if (what == EditorSettings.NotificationEditorSettingsChanged) - { - var editorBaseControl = editorInterface.GetBaseControl(); - panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); - } - } - - public void AddBuildTab(BuildTab buildTab) - { - buildTabs.AddChild(buildTab); - RaiseBuildTab(buildTab); - } - - public void RaiseBuildTab(BuildTab buildTab) - { - if (buildTab.GetParent() != buildTabs) - throw new InvalidOperationException("Build tab is not in the tabs list"); - - buildTabs.MoveChild(buildTab, 0); - _UpdateBuildTabsList(); - } - - public void ShowBuildTab() - { - for (int i = 0; i < panelTabs.GetTabCount(); i++) - { - if (panelTabs.GetTabControl(i) == panelBuildsTab) - { - panelTabs.CurrentTab = i; - GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this); - return; - } - } - - GD.PushError("Builds tab not found"); - } - - public override void _Ready() - { - base._Ready(); - - editorInterface = GodotSharpEditor.Instance.GetEditorInterface(); - - var editorBaseControl = editorInterface.GetBaseControl(); - - SizeFlagsVertical = (int)SizeFlags.ExpandFill; - SetAnchorsAndMarginsPreset(LayoutPreset.Wide); - - panelTabs = new TabContainer - { - TabAlign = TabContainer.TabAlignEnum.Left, - RectMinSize = new Vector2(0, 228) * EditorScale, - SizeFlagsVertical = (int)SizeFlags.ExpandFill - }; - panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); - AddChild(panelTabs); - - { - // Builds tab - panelBuildsTab = new VBoxContainer - { - Name = "Builds".TTR(), - SizeFlagsHorizontal = (int)SizeFlags.ExpandFill - }; - panelTabs.AddChild(panelBuildsTab); - - var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill}; - panelBuildsTab.AddChild(toolBarHBox); - - var buildProjectBtn = new Button - { - Text = "Build Project".TTR(), - FocusMode = FocusModeEnum.None - }; - buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed)); - toolBarHBox.AddChild(buildProjectBtn); - - toolBarHBox.AddSpacer(begin: false); - - warningsBtn = new ToolButton - { - Text = "Warnings".TTR(), - ToggleMode = true, - Pressed = true, - Visible = false, - FocusMode = FocusModeEnum.None - }; - warningsBtn.Connect("toggled", this, nameof(_WarningsToggled)); - toolBarHBox.AddChild(warningsBtn); - - errorsBtn = new ToolButton - { - Text = "Errors".TTR(), - ToggleMode = true, - Pressed = true, - Visible = false, - FocusMode = FocusModeEnum.None - }; - errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled)); - toolBarHBox.AddChild(errorsBtn); - - toolBarHBox.AddSpacer(begin: false); - - viewLogBtn = new Button - { - Text = "View log".TTR(), - FocusMode = FocusModeEnum.None, - Visible = false - }; - viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed)); - toolBarHBox.AddChild(viewLogBtn); - - var hsc = new HSplitContainer - { - SizeFlagsHorizontal = (int)SizeFlags.ExpandFill, - SizeFlagsVertical = (int)SizeFlags.ExpandFill - }; - panelBuildsTab.AddChild(hsc); - - buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; - buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected)); - buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected)); - hsc.AddChild(buildTabsList); - - buildTabs = new TabContainer - { - TabAlign = TabContainer.TabAlignEnum.Left, - SizeFlagsHorizontal = (int)SizeFlags.ExpandFill, - TabsVisible = false - }; - hsc.AddChild(buildTabs); - } - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs similarity index 71% rename from modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs rename to modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index ab090c46e73c..51055dc9b9bb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -4,7 +4,7 @@ using GodotTools.Internals; using Path = System.IO.Path; -namespace GodotTools +namespace GodotTools.Build { [Serializable] public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization @@ -20,7 +20,9 @@ public sealed class BuildInfo : Reference // TODO Remove Reference once we have public override bool Equals(object obj) { if (obj is BuildInfo other) - return other.Solution == Solution && other.Configuration == Configuration; + return other.Solution == Solution && other.Targets == Targets && + other.Configuration == Configuration && other.Restore == Restore && + other.CustomProperties == CustomProperties && other.LogsDirPath == LogsDirPath; return false; } @@ -31,7 +33,11 @@ public override int GetHashCode() { int hash = 17; hash = hash * 29 + Solution.GetHashCode(); + hash = hash * 29 + Targets.GetHashCode(); hash = hash * 29 + Configuration.GetHashCode(); + hash = hash * 29 + Restore.GetHashCode(); + hash = hash * 29 + CustomProperties.GetHashCode(); + hash = hash * 29 + LogsDirPath.GetHashCode(); return hash; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs similarity index 64% rename from modules/mono/editor/GodotTools/GodotTools/BuildManager.cs rename to modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 81af3ab0eb8d..9b512d90f527 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -1,20 +1,19 @@ using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using GodotTools.Build; using GodotTools.Ides.Rider; using GodotTools.Internals; -using GodotTools.Utils; using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; +using OS = GodotTools.Utils.OS; +using Path = System.IO.Path; -namespace GodotTools +namespace GodotTools.Build { public static class BuildManager { - private static readonly List BuildsInProgress = new List(); + private static BuildInfo _buildInProgress; public const string PropNameMSBuildMono = "MSBuild (Mono)"; public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)"; @@ -24,6 +23,14 @@ public static class BuildManager public const string MsBuildIssuesFileName = "msbuild_issues.csv"; public const string MsBuildLogFileName = "msbuild_log.txt"; + public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason); + + public static event BuildLaunchFailedEventHandler BuildLaunchFailed; + public static event Action BuildStarted; + public static event Action BuildFinished; + public static event Action StdOutputReceived; + public static event Action StdErrorReceived; + private static void RemoveOldIssuesFile(BuildInfo buildInfo) { var issuesFile = GetIssuesFilePath(buildInfo); @@ -36,12 +43,13 @@ private static void RemoveOldIssuesFile(BuildInfo buildInfo) private static void ShowBuildErrorDialog(string message) { - GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); - GodotSharpEditor.Instance.BottomPanel.ShowBuildTab(); + var plugin = GodotSharpEditor.Instance; + plugin.ShowErrorDialog(message, "Build error"); + plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel); } - public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException(); - public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException(); + public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); + public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); private static string GetLogFilePath(BuildInfo buildInfo) { @@ -61,15 +69,14 @@ private static void PrintVerbose(string text) public static bool Build(BuildInfo buildInfo) { - if (BuildsInProgress.Contains(buildInfo)) + if (_buildInProgress != null) throw new InvalidOperationException("A build is already in progress"); - BuildsInProgress.Add(buildInfo); + _buildInProgress = buildInfo; try { - BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); - buildTab.OnBuildStart(); + BuildStarted?.Invoke(buildInfo); // Required in order to update the build tasks list Internal.GodotMainIteration(); @@ -80,44 +87,44 @@ public static bool Build(BuildInfo buildInfo) } catch (IOException e) { - buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); Console.Error.WriteLine(e); } try { - int exitCode = BuildSystem.Build(buildInfo); + int exitCode = BuildSystem.Build(buildInfo, StdOutputReceived, StdErrorReceived); if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); + BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error); return exitCode == 0; } catch (Exception e) { - buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } } finally { - BuildsInProgress.Remove(buildInfo); + _buildInProgress = null; } } public static async Task BuildAsync(BuildInfo buildInfo) { - if (BuildsInProgress.Contains(buildInfo)) + if (_buildInProgress != null) throw new InvalidOperationException("A build is already in progress"); - BuildsInProgress.Add(buildInfo); + _buildInProgress = buildInfo; try { - BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); + BuildStarted?.Invoke(buildInfo); try { @@ -125,43 +132,57 @@ public static async Task BuildAsync(BuildInfo buildInfo) } catch (IOException e) { - buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); Console.Error.WriteLine(e); } try { - int exitCode = await BuildSystem.BuildAsync(buildInfo); + int exitCode = await BuildSystem.BuildAsync(buildInfo, StdOutputReceived, StdErrorReceived); if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); + BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error); return exitCode == 0; } catch (Exception e) { - buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } } finally { - BuildsInProgress.Remove(buildInfo); + _buildInProgress = null; } } - public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null) + public static bool BuildProjectBlocking(string config, [CanBeNull] string[] targets = null, [CanBeNull] string platform = null) { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets ?? new[] {"Build"}, config, restore: true); + + // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. + if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform)) + buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + + if (Internal.GodotIsRealTDouble()) + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + + return BuildProjectBlocking(buildInfo); + } + + private static bool BuildProjectBlocking(BuildInfo buildInfo) + { + if (!File.Exists(buildInfo.Solution)) return true; // No solution to build // Make sure the API assemblies are up to date before building the project. // We may not have had the chance to update the release API assemblies, and the debug ones // may have been deleted by the user at some point after they were loaded by the Godot editor. - string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "ExportRelease" ? "Release" : "Debug"); + string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug"); if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) { @@ -173,15 +194,6 @@ public static bool BuildProjectBlocking(string config, [CanBeNull] string platfo { pr.Step("Building project solution", 0); - var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, new[] {"Build"}, config, restore: true); - - // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. - if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform)) - buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); - - if (Internal.GodotIsRealTDouble()) - buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); - if (!Build(buildInfo)) { ShowBuildErrorDialog("Failed to build project solution"); @@ -197,18 +209,41 @@ public static bool EditorBuildCallback() if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build + GenerateEditorScriptMetadata(); + + if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) + return true; // Requested play from an external editor/IDE which already built the project + + return BuildProjectBlocking("Debug"); + } + + // NOTE: This will be replaced with C# source generators in 4.0 + public static void GenerateEditorScriptMetadata() + { string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); - if (File.Exists(editorScriptsMetadataPath)) - File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + if (!File.Exists(editorScriptsMetadataPath)) + return; - if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) - return true; // Requested play from an external editor/IDE which already built the project + try + { + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + } + catch (IOException e) + { + throw new IOException("Failed to copy scripts metadata file.", innerException: e); + } + } - return BuildProjectBlocking("Debug"); + // NOTE: This will be replaced with C# source generators in 4.0 + public static string GenerateExportedGameScriptMetadata(bool isDebug) + { + string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + return scriptsMetadataPath; } public static void Initialize() @@ -254,8 +289,6 @@ public static void Initialize() ["hint"] = Godot.PropertyHint.Enum, ["hint_string"] = hintString }); - - EditorDef("mono/builds/print_build_output", false); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs similarity index 52% rename from modules/mono/editor/GodotTools/GodotTools/BuildTab.cs rename to modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index b35bd3244b0f..bb7173eedb64 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -5,16 +5,10 @@ using File = GodotTools.Utils.File; using Path = System.IO.Path; -namespace GodotTools +namespace GodotTools.Build { - public class BuildTab : VBoxContainer + public class BuildOutputView : VBoxContainer, ISerializationListener { - public enum BuildResults - { - Error, - Success - } - [Serializable] private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization { @@ -29,10 +23,15 @@ private class BuildIssue : Reference // TODO Remove Reference once we have prope private readonly Array issues = new Array(); // TODO Use List once we have proper serialization private ItemList issuesList; + private TextEdit buildLog; + private PopupMenu issuesListContextMenu; - public bool BuildExited { get; private set; } = false; + [Signal] + public delegate void BuildStateChanged(); - public BuildResults? BuildResult { get; private set; } = null; + public bool HasBuildExited { get; private set; } = false; + + public BuildResult? BuildResult { get; private set; } = null; public int ErrorCount { get; private set; } = 0; @@ -41,23 +40,31 @@ private class BuildIssue : Reference // TODO Remove Reference once we have prope public bool ErrorsVisible { get; set; } = true; public bool WarningsVisible { get; set; } = true; - public Texture IconTexture + public Texture BuildStateIcon { get { - if (!BuildExited) + if (!HasBuildExited) return GetIcon("Stop", "EditorIcons"); - if (BuildResult == BuildResults.Error) - return GetIcon("StatusError", "EditorIcons"); + if (BuildResult == Build.BuildResult.Error) + return GetIcon("Error", "EditorIcons"); + + if (WarningCount > 1) + return GetIcon("Warning", "EditorIcons"); - return GetIcon("StatusSuccess", "EditorIcons"); + return null; } } - public BuildInfo BuildInfo { get; private set; } + private BuildInfo BuildInfo { get; set; } - private void _LoadIssuesFromFile(string csvFile) + public bool LogVisible + { + set => buildLog.Visible = value; + } + + private void LoadIssuesFromFile(string csvFile) { using (var file = new Godot.File()) { @@ -107,7 +114,7 @@ private void _LoadIssuesFromFile(string csvFile) } } - private void _IssueActivated(int idx) + private void IssueActivated(int idx) { if (idx < 0 || idx >= issuesList.GetItemCount()) throw new IndexOutOfRangeException("Item list index out of range"); @@ -190,49 +197,79 @@ public void UpdateIssuesList() } } - public void OnBuildStart() + private void BuildLaunchFailed(BuildInfo buildInfo, string cause) + { + HasBuildExited = true; + BuildResult = Build.BuildResult.Error; + + issuesList.Clear(); + + var issue = new BuildIssue {Message = cause, Warning = false}; + + ErrorCount += 1; + issues.Add(issue); + + UpdateIssuesList(); + + EmitSignal(nameof(BuildStateChanged)); + } + + private void BuildStarted(BuildInfo buildInfo) { - BuildExited = false; + BuildInfo = buildInfo; + HasBuildExited = false; issues.Clear(); WarningCount = 0; ErrorCount = 0; + buildLog.Text = string.Empty; + UpdateIssuesList(); - GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); + EmitSignal(nameof(BuildStateChanged)); } - public void OnBuildExit(BuildResults result) + private void BuildFinished(BuildResult result) { - BuildExited = true; + HasBuildExited = true; BuildResult = result; - _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + UpdateIssuesList(); - GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); + EmitSignal(nameof(BuildStateChanged)); } - public void OnBuildExecFailed(string cause) + private void StdOutputReceived(string text) { - BuildExited = true; - BuildResult = BuildResults.Error; - - issuesList.Clear(); + buildLog.Text += text + "\n"; + ScrollToLastNonEmptyLogLine(); + } - var issue = new BuildIssue { Message = cause, Warning = false }; + private void StdErrorReceived(string text) + { + buildLog.Text += text + "\n"; + ScrollToLastNonEmptyLogLine(); + } - ErrorCount += 1; - issues.Add(issue); + private void ScrollToLastNonEmptyLogLine() + { + int line; + for (line = buildLog.GetLineCount(); line > 0; line--) + { + string lineText = buildLog.GetLine(line); - UpdateIssuesList(); + if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) + break; + } - GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this); + buildLog.CursorSetLine(line); } public void RestartBuild() { - if (!BuildExited) + if (!HasBuildExited) throw new InvalidOperationException("Build already started"); BuildManager.RestartBuild(this); @@ -240,28 +277,118 @@ public void RestartBuild() public void StopBuild() { - if (!BuildExited) + if (!HasBuildExited) throw new InvalidOperationException("Build is not in progress"); BuildManager.StopBuild(this); } + private enum IssuesContextMenuOption + { + Copy + } + + private void IssuesListContextOptionPressed(IssuesContextMenuOption id) + { + switch (id) + { + case IssuesContextMenuOption.Copy: + { + // We don't allow multi-selection but just in case that changes later... + string text = null; + + foreach (int issueIndex in issuesList.GetSelectedItems()) + { + if (text != null) + text += "\n"; + text += issuesList.GetItemText(issueIndex); + } + + if (text != null) + OS.Clipboard = text; + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option"); + } + } + + private void IssuesListRmbSelected(int index, Vector2 atPosition) + { + _ = index; // Unused + + issuesListContextMenu.Clear(); + issuesListContextMenu.SetSize(new Vector2(1, 1)); + + if (issuesList.IsAnythingSelected()) + { + // Add menu entries for the selected item + issuesListContextMenu.AddIconItem(GetIcon("ActionCopy", "EditorIcons"), + label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); + } + + if (issuesListContextMenu.GetItemCount() > 0) + { + issuesListContextMenu.SetPosition(issuesList.RectGlobalPosition + atPosition); + issuesListContextMenu.Popup_(); + } + } + public override void _Ready() { base._Ready(); - issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill }; - issuesList.Connect("item_activated", this, nameof(_IssueActivated)); - AddChild(issuesList); + SizeFlagsVertical = (int)SizeFlags.ExpandFill; + + var hsc = new HSplitContainer + { + SizeFlagsHorizontal = (int)SizeFlags.ExpandFill, + SizeFlagsVertical = (int)SizeFlags.ExpandFill + }; + AddChild(hsc); + + issuesList = new ItemList + { + SizeFlagsVertical = (int)SizeFlags.ExpandFill, + SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the build log + }; + issuesList.Connect("item_activated", this, nameof(IssueActivated)); + issuesList.AllowRmbSelect = true; + issuesList.Connect("item_rmb_selected", this, nameof(IssuesListRmbSelected)); + hsc.AddChild(issuesList); + + issuesListContextMenu = new PopupMenu(); + issuesListContextMenu.Connect("id_pressed", this, nameof(IssuesListContextOptionPressed)); + issuesList.AddChild(issuesListContextMenu); + + buildLog = new TextEdit + { + Readonly = true, + SizeFlagsVertical = (int)SizeFlags.ExpandFill, + SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the issues list + }; + hsc.AddChild(buildLog); + + AddBuildEventListeners(); } - private BuildTab() + private void AddBuildEventListeners() { + BuildManager.BuildLaunchFailed += BuildLaunchFailed; + BuildManager.BuildStarted += BuildStarted; + BuildManager.BuildFinished += BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived += line => CallDeferred(nameof(StdOutputReceived), line); + BuildManager.StdErrorReceived += line => CallDeferred(nameof(StdErrorReceived), line); } - public BuildTab(BuildInfo buildInfo) + public void OnBeforeSerialize() { - BuildInfo = buildInfo; + } + + public void OnAfterDeserialize() + { + AddBuildEventListeners(); // Re-add them } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs new file mode 100644 index 000000000000..59055b60c3d5 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs @@ -0,0 +1,8 @@ +namespace GodotTools.Build +{ + public enum BuildResult + { + Error, + Success + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index d9862ae36180..bac7a2e6db20 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -44,10 +44,7 @@ private static bool UsingMonoMsBuildOnWindows } } - private static bool PrintBuildOutput => - (bool)EditorSettings.GetSetting("mono/builds/print_build_output"); - - private static Process LaunchBuild(BuildInfo buildInfo) + private static Process LaunchBuild(BuildInfo buildInfo, Action stdOutHandler, Action stdErrHandler) { (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild(); @@ -58,13 +55,13 @@ private static Process LaunchBuild(BuildInfo buildInfo) var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs); - bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput || Godot.OS.IsStdoutVerbose()) - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); + string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"; + stdOutHandler?.Invoke(launchMessage); + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine(launchMessage); - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; if (UsingMonoMsBuildOnWindows) @@ -82,20 +79,22 @@ private static Process LaunchBuild(BuildInfo buildInfo) var process = new Process {StartInfo = startInfo}; + if (stdOutHandler != null) + process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data); + if (stdErrHandler != null) + process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data); + process.Start(); - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); return process; } - public static int Build(BuildInfo buildInfo) + public static int Build(BuildInfo buildInfo, Action stdOutHandler, Action stdErrHandler) { - using (var process = LaunchBuild(buildInfo)) + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { process.WaitForExit(); @@ -103,9 +102,9 @@ public static int Build(BuildInfo buildInfo) } } - public static async Task BuildAsync(BuildInfo buildInfo) + public static async Task BuildAsync(BuildInfo buildInfo, Action stdOutHandler, Action stdErrHandler) { - using (var process = LaunchBuild(buildInfo)) + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { await process.WaitForExitAsync(); @@ -152,10 +151,5 @@ private static void RemovePlatformVariable(StringDictionary environmentVariables foreach (string env in platformEnvironmentVariables) environmentVariables.Remove(env); } - - private static bool IsDebugMsBuildRequested() - { - return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; - } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs new file mode 100644 index 000000000000..515412777cf1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -0,0 +1,165 @@ +using System; +using Godot; +using GodotTools.Internals; +using JetBrains.Annotations; +using static GodotTools.Internals.Globals; +using File = GodotTools.Utils.File; + +namespace GodotTools.Build +{ + public class MSBuildPanel : VBoxContainer + { + public BuildOutputView BuildOutputView { get; private set; } + + private Button errorsBtn; + private Button warningsBtn; + private Button viewLogBtn; + + private void WarningsToggled(bool pressed) + { + BuildOutputView.WarningsVisible = pressed; + BuildOutputView.UpdateIssuesList(); + } + + private void ErrorsToggled(bool pressed) + { + BuildOutputView.ErrorsVisible = pressed; + BuildOutputView.UpdateIssuesList(); + } + + [UsedImplicitly] + public void BuildSolution() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + BuildManager.GenerateEditorScriptMetadata(); + + if (!BuildManager.BuildProjectBlocking("Debug")) + return; // Build failed + + // Notify running game for hot-reload + Internal.ScriptEditorDebuggerReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + [UsedImplicitly] + private void RebuildSolution() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + BuildManager.GenerateEditorScriptMetadata(); + + if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"})) + return; // Build failed + + // Notify running game for hot-reload + Internal.ScriptEditorDebuggerReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + [UsedImplicitly] + private void CleanSolution() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Clean"}); + } + + private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; + + private void BuildMenuOptionPressed(BuildMenuOptions id) + { + switch (id) + { + case BuildMenuOptions.BuildSolution: + BuildSolution(); + break; + case BuildMenuOptions.RebuildSolution: + RebuildSolution(); + break; + case BuildMenuOptions.CleanSolution: + CleanSolution(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + } + } + + private enum BuildMenuOptions + { + BuildSolution, + RebuildSolution, + CleanSolution + } + + public override void _Ready() + { + base._Ready(); + + RectMinSize = new Vector2(0, 228) * EditorScale; + SizeFlagsVertical = (int)SizeFlags.ExpandFill; + + var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill}; + AddChild(toolBarHBox); + + var buildMenuBtn = new MenuButton {Text = "Build", Icon = GetIcon("Play", "EditorIcons")}; + toolBarHBox.AddChild(buildMenuBtn); + + var buildMenu = buildMenuBtn.GetPopup(); + buildMenu.AddItem("Build Solution".TTR(), (int)BuildMenuOptions.BuildSolution); + buildMenu.AddItem("Rebuild Solution".TTR(), (int)BuildMenuOptions.RebuildSolution); + buildMenu.AddItem("Clean Solution".TTR(), (int)BuildMenuOptions.CleanSolution); + buildMenu.Connect("id_pressed", this, nameof(BuildMenuOptionPressed)); + + errorsBtn = new Button + { + HintTooltip = "Show Errors".TTR(), + Icon = GetIcon("StatusError", "EditorIcons"), + ExpandIcon = false, + ToggleMode = true, + Pressed = true, + FocusMode = FocusModeEnum.None + }; + errorsBtn.Connect("toggled", this, nameof(ErrorsToggled)); + toolBarHBox.AddChild(errorsBtn); + + warningsBtn = new Button + { + HintTooltip = "Show Warnings".TTR(), + Icon = GetIcon("NodeWarning", "EditorIcons"), + ExpandIcon = false, + ToggleMode = true, + Pressed = true, + FocusMode = FocusModeEnum.None + }; + warningsBtn.Connect("toggled", this, nameof(WarningsToggled)); + toolBarHBox.AddChild(warningsBtn); + + viewLogBtn = new Button + { + Text = "Show Output".TTR(), + ToggleMode = true, + Pressed = true, + FocusMode = FocusModeEnum.None + }; + viewLogBtn.Connect("toggled", this, nameof(ViewLogToggled)); + toolBarHBox.AddChild(viewLogBtn); + + BuildOutputView = new BuildOutputView(); + AddChild(BuildOutputView); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index 7629223d8ce9..d2d6f46ece01 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -31,7 +31,7 @@ public static (string, BuildTool) FindMsBuild() string dotnetCliPath = OS.PathWhich("dotnet"); if (!string.IsNullOrEmpty(dotnetCliPath)) return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Visual Studio."); + GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Visual Studio."); goto case BuildTool.MsBuildVs; } case BuildTool.MsBuildVs: @@ -89,7 +89,7 @@ public static (string, BuildTool) FindMsBuild() string dotnetCliPath = OS.PathWhich("dotnet"); if (!string.IsNullOrEmpty(dotnetCliPath)) return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Mono."); + GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Mono."); goto case BuildTool.MsBuildMono; } case BuildTool.MsBuildMono: @@ -161,7 +161,7 @@ private static string FindMsBuildToolsPathOnWindows() // Try to find 15.0 with vswhere - var envNames = Internal.GodotIs32Bits() ? new[] { "ProgramFiles", "ProgramW6432" } : new[] { "ProgramFiles(x86)", "ProgramFiles" }; + var envNames = Internal.GodotIs32Bits() ? new[] {"ProgramFiles", "ProgramW6432"} : new[] {"ProgramFiles(x86)", "ProgramFiles"}; string vsWherePath = null; foreach (var envName in envNames) @@ -177,7 +177,7 @@ private static string FindMsBuildToolsPathOnWindows() vsWherePath = null; } - var vsWhereArgs = new[] { "-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild" }; + var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; var outputArray = new Godot.Collections.Array(); int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index fc64b1a426e1..ec773cf44c20 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using GodotTools.Build; using GodotTools.Core; using GodotTools.Internals; using JetBrains.Annotations; @@ -143,6 +144,8 @@ public override void _ExportBegin(string[] features, bool isDebug, string path, private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) { + _ = flags; // Unused + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; @@ -154,12 +157,10 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, int string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); - + string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug); AddFile(scriptsMetadataPath, scriptsMetadataPath); - if (!BuildManager.BuildProjectBlocking(buildConfig, platform)) + if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) throw new Exception("Failed to build project"); // Add dependency assemblies diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index fdc0161a3b1a..2668a1790b60 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -4,9 +4,9 @@ using GodotTools.Utils; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using GodotTools.Build; using GodotTools.Ides; using GodotTools.Ides.Rider; using GodotTools.Internals; @@ -19,7 +19,6 @@ namespace GodotTools { - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings editorSettings; @@ -37,7 +36,7 @@ public class GodotSharpEditor : EditorPlugin, ISerializationListener private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization - public BottomPanel BottomPanel { get; private set; } + public MSBuildPanel MSBuildPanel { get; private set; } public bool SkipBuildBeforePlaying { get; set; } = false; @@ -160,7 +159,7 @@ private void _MenuOptionPressed(MenuOptions id) } } - private void _BuildSolutionPressed() + private void BuildSolutionPressed() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { @@ -168,7 +167,7 @@ private void _BuildSolutionPressed() return; // Failed to create solution } - Instance.BottomPanel.BuildProjectPressed(); + Instance.MSBuildPanel.BuildSolution(); } private void _FileSystemDockFileMoved(string file, string newFile) @@ -199,28 +198,28 @@ private void _FileSystemDockFolderRemoved(string oldFolder) ProjectSettings.GlobalizePath(oldFolder)); } - public override void _Notification(int what) + public override void _Ready() { - base._Notification(what); + base._Ready(); - if (what == NotificationReady) + MSBuildPanel.BuildOutputView.Connect( + nameof(BuildOutputView.BuildStateChanged), this, nameof(BuildStateChanged)); + + bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showInfoDialog) { - bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); - if (showInfoDialog) - { - aboutDialog.PopupExclusive = true; - _ShowAboutDialog(); - // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. - aboutDialog.PopupExclusive = false; - } + aboutDialog.PopupExclusive = true; + _ShowAboutDialog(); + // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. + aboutDialog.PopupExclusive = false; + } - var fileSystemDock = GetEditorInterface().GetFileSystemDock(); + var fileSystemDock = GetEditorInterface().GetFileSystemDock(); - fileSystemDock.Connect("files_moved", this, nameof(_FileSystemDockFileMoved)); - fileSystemDock.Connect("file_removed", this, nameof(_FileSystemDockFileRemoved)); - fileSystemDock.Connect("folder_moved", this, nameof(_FileSystemDockFolderMoved)); - fileSystemDock.Connect("folder_removed", this, nameof(_FileSystemDockFolderRemoved)); - } + fileSystemDock.Connect("files_moved", this, nameof(_FileSystemDockFileMoved)); + fileSystemDock.Connect("file_removed", this, nameof(_FileSystemDockFileRemoved)); + fileSystemDock.Connect("folder_moved", this, nameof(_FileSystemDockFolderMoved)); + fileSystemDock.Connect("folder_removed", this, nameof(_FileSystemDockFolderRemoved)); } private enum MenuOptions @@ -437,6 +436,12 @@ private void ApplyNecessaryChangesToSolution() } } + private void BuildStateChanged() + { + if (bottomPanelBtn != null) + bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + } + public override void EnablePlugin() { base.EnablePlugin(); @@ -453,9 +458,8 @@ public override void EnablePlugin() errorDialog = new AcceptDialog(); editorBaseControl.AddChild(errorDialog); - BottomPanel = new BottomPanel(); - - bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR()); + MSBuildPanel = new MSBuildPanel(); + bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); @@ -463,7 +467,7 @@ public override void EnablePlugin() menuPopup.Hide(); menuPopup.SetAsToplevel(true); - AddToolSubmenuItem("Mono", menuPopup); + AddToolSubmenuItem("C#", menuPopup); // TODO: Remove or edit this info dialog once Mono support is no longer in alpha { @@ -516,7 +520,7 @@ public override void EnablePlugin() HintTooltip = "Build solution", FocusMode = Control.FocusModeEnum.None }; - toolBarButton.Connect("pressed", this, nameof(_BuildSolutionPressed)); + toolBarButton.Connect("pressed", this, nameof(BuildSolutionPressed)); AddControlToContainer(CustomControlContainer.Toolbar, toolBarButton); if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) @@ -610,6 +614,7 @@ public void OnAfterDeserialize() public static GodotSharpEditor Instance { get; private set; } + [UsedImplicitly] private GodotSharpEditor() { }