C#: Re-work solution build output panel
- Removed item list that displayed multiple build configurations launched. Now we only display the last build that was launched. - Display build output next to the issues list. Its visibility can be toggled off/on. This build output is obtained from the MSBuild process rather than the MSBuild logger. As such it displays some MSBuild fatal errors that previously couldn't be displayed. - Added a context menu to the issues list with the option to copy the issue text. - Replaced the 'Build Project' button in the panel with a popup menu with the options: - Build Solution - Rebuild Solution - Clean Solution - The bottom panel button was renamed from 'Mono' to 'MSBuild' and now display an error/warning icon if the last build had issues.
This commit is contained in:
parent
32be9299ba
commit
f06f91281c
11 changed files with 512 additions and 539 deletions
|
@ -15,14 +15,14 @@ namespace GodotTools.BuildLogger
|
||||||
public void Initialize(IEventSource eventSource)
|
public void Initialize(IEventSource eventSource)
|
||||||
{
|
{
|
||||||
if (null == Parameters)
|
if (null == Parameters)
|
||||||
throw new LoggerException("Log directory was not set.");
|
throw new LoggerException("Log directory parameter not specified.");
|
||||||
|
|
||||||
var parameters = Parameters.Split(new[] { ';' });
|
var parameters = Parameters.Split(new[] { ';' });
|
||||||
|
|
||||||
string logDir = parameters[0];
|
string logDir = parameters[0];
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(logDir))
|
if (string.IsNullOrEmpty(logDir))
|
||||||
throw new LoggerException("Log directory was not set.");
|
throw new LoggerException("Log directory parameter is empty.");
|
||||||
|
|
||||||
if (parameters.Length > 1)
|
if (parameters.Length > 1)
|
||||||
throw new LoggerException("Too many parameters passed.");
|
throw new LoggerException("Too many parameters passed.");
|
||||||
|
@ -51,22 +51,31 @@ namespace GodotTools.BuildLogger
|
||||||
{
|
{
|
||||||
throw new LoggerException("Failed to create log file: " + ex.Message);
|
throw new LoggerException("Failed to create log file: " + ex.Message);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Unexpected failure
|
// Unexpected failure
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
eventSource.ProjectStarted += eventSource_ProjectStarted;
|
eventSource.ProjectStarted += eventSource_ProjectStarted;
|
||||||
eventSource.TaskStarted += eventSource_TaskStarted;
|
eventSource.ProjectFinished += eventSource_ProjectFinished;
|
||||||
eventSource.MessageRaised += eventSource_MessageRaised;
|
eventSource.MessageRaised += eventSource_MessageRaised;
|
||||||
eventSource.WarningRaised += eventSource_WarningRaised;
|
eventSource.WarningRaised += eventSource_WarningRaised;
|
||||||
eventSource.ErrorRaised += eventSource_ErrorRaised;
|
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}";
|
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
|
||||||
|
|
||||||
|
@ -81,7 +90,7 @@ namespace GodotTools.BuildLogger
|
||||||
issuesStreamWriter.WriteLine(errorLine);
|
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}";
|
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}";
|
||||||
|
|
||||||
|
@ -108,40 +117,6 @@ namespace GodotTools.BuildLogger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write a line to the log, adding the SenderName
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a line to the log, adding the SenderName and Message
|
/// Write a line to the log, adding the SenderName and Message
|
||||||
/// (these parameters are on all MSBuild event argument objects)
|
/// (these parameters are on all MSBuild event argument objects)
|
||||||
|
|
|
@ -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 Button warningsBtn;
|
|
||||||
private Button 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<BuildTab>(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.EditorDebuggerNodeReloadScripts();
|
|
||||||
|
|
||||||
// Hot-reload in the editor
|
|
||||||
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("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.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles"));
|
|
||||||
panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles"));
|
|
||||||
panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("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.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles"));
|
|
||||||
panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles"));
|
|
||||||
panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("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.PressedSignal += BuildProjectPressed;
|
|
||||||
toolBarHBox.AddChild(buildProjectBtn);
|
|
||||||
|
|
||||||
toolBarHBox.AddSpacer(begin: false);
|
|
||||||
|
|
||||||
warningsBtn = new Button
|
|
||||||
{
|
|
||||||
Text = "Warnings".TTR(),
|
|
||||||
ToggleMode = true,
|
|
||||||
Pressed = true,
|
|
||||||
Visible = false,
|
|
||||||
FocusMode = FocusModeEnum.None
|
|
||||||
};
|
|
||||||
warningsBtn.Toggled += _WarningsToggled;
|
|
||||||
toolBarHBox.AddChild(warningsBtn);
|
|
||||||
|
|
||||||
errorsBtn = new Button
|
|
||||||
{
|
|
||||||
Text = "Errors".TTR(),
|
|
||||||
ToggleMode = true,
|
|
||||||
Pressed = true,
|
|
||||||
Visible = false,
|
|
||||||
FocusMode = FocusModeEnum.None
|
|
||||||
};
|
|
||||||
errorsBtn.Toggled += _ErrorsToggled;
|
|
||||||
toolBarHBox.AddChild(errorsBtn);
|
|
||||||
|
|
||||||
toolBarHBox.AddSpacer(begin: false);
|
|
||||||
|
|
||||||
viewLogBtn = new Button
|
|
||||||
{
|
|
||||||
Text = "View log".TTR(),
|
|
||||||
FocusMode = FocusModeEnum.None,
|
|
||||||
Visible = false
|
|
||||||
};
|
|
||||||
viewLogBtn.PressedSignal += _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.ItemSelected += _BuildTabsItemSelected;
|
|
||||||
buildTabsList.NothingSelected += _BuildTabsNothingSelected;
|
|
||||||
hsc.AddChild(buildTabsList);
|
|
||||||
|
|
||||||
buildTabs = new TabContainer
|
|
||||||
{
|
|
||||||
TabAlign = TabContainer.TabAlignEnum.Left,
|
|
||||||
SizeFlagsHorizontal = (int)SizeFlags.ExpandFill,
|
|
||||||
TabsVisible = false
|
|
||||||
};
|
|
||||||
hsc.AddChild(buildTabs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ using Godot.Collections;
|
||||||
using GodotTools.Internals;
|
using GodotTools.Internals;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace GodotTools
|
namespace GodotTools.Build
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
|
public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
|
||||||
|
@ -20,7 +20,9 @@ namespace GodotTools
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
if (obj is BuildInfo other)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -31,7 +33,11 @@ namespace GodotTools
|
||||||
{
|
{
|
||||||
int hash = 17;
|
int hash = 17;
|
||||||
hash = hash * 29 + Solution.GetHashCode();
|
hash = hash * 29 + Solution.GetHashCode();
|
||||||
|
hash = hash * 29 + Targets.GetHashCode();
|
||||||
hash = hash * 29 + Configuration.GetHashCode();
|
hash = hash * 29 + Configuration.GetHashCode();
|
||||||
|
hash = hash * 29 + Restore.GetHashCode();
|
||||||
|
hash = hash * 29 + CustomProperties.GetHashCode();
|
||||||
|
hash = hash * 29 + LogsDirPath.GetHashCode();
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,19 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using GodotTools.Build;
|
|
||||||
using GodotTools.Ides.Rider;
|
using GodotTools.Ides.Rider;
|
||||||
using GodotTools.Internals;
|
using GodotTools.Internals;
|
||||||
using GodotTools.Utils;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using static GodotTools.Internals.Globals;
|
using static GodotTools.Internals.Globals;
|
||||||
using File = GodotTools.Utils.File;
|
using File = GodotTools.Utils.File;
|
||||||
|
using OS = GodotTools.Utils.OS;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace GodotTools
|
namespace GodotTools.Build
|
||||||
{
|
{
|
||||||
public static class BuildManager
|
public static class BuildManager
|
||||||
{
|
{
|
||||||
private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>();
|
private static BuildInfo _buildInProgress;
|
||||||
|
|
||||||
public const string PropNameMSBuildMono = "MSBuild (Mono)";
|
public const string PropNameMSBuildMono = "MSBuild (Mono)";
|
||||||
public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)";
|
public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)";
|
||||||
|
@ -24,6 +23,14 @@ namespace GodotTools
|
||||||
public const string MsBuildIssuesFileName = "msbuild_issues.csv";
|
public const string MsBuildIssuesFileName = "msbuild_issues.csv";
|
||||||
public const string MsBuildLogFileName = "msbuild_log.txt";
|
public const string MsBuildLogFileName = "msbuild_log.txt";
|
||||||
|
|
||||||
|
public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason);
|
||||||
|
|
||||||
|
public static event BuildLaunchFailedEventHandler BuildLaunchFailed;
|
||||||
|
public static event Action<BuildInfo> BuildStarted;
|
||||||
|
public static event Action<BuildResult> BuildFinished;
|
||||||
|
public static event Action<string> StdOutputReceived;
|
||||||
|
public static event Action<string> StdErrorReceived;
|
||||||
|
|
||||||
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
|
private static void RemoveOldIssuesFile(BuildInfo buildInfo)
|
||||||
{
|
{
|
||||||
var issuesFile = GetIssuesFilePath(buildInfo);
|
var issuesFile = GetIssuesFilePath(buildInfo);
|
||||||
|
@ -36,12 +43,13 @@ namespace GodotTools
|
||||||
|
|
||||||
private static void ShowBuildErrorDialog(string message)
|
private static void ShowBuildErrorDialog(string message)
|
||||||
{
|
{
|
||||||
GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
|
var plugin = GodotSharpEditor.Instance;
|
||||||
GodotSharpEditor.Instance.BottomPanel.ShowBuildTab();
|
plugin.ShowErrorDialog(message, "Build error");
|
||||||
|
plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException();
|
public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException();
|
||||||
public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException();
|
public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException();
|
||||||
|
|
||||||
private static string GetLogFilePath(BuildInfo buildInfo)
|
private static string GetLogFilePath(BuildInfo buildInfo)
|
||||||
{
|
{
|
||||||
|
@ -61,15 +69,14 @@ namespace GodotTools
|
||||||
|
|
||||||
public static bool Build(BuildInfo buildInfo)
|
public static bool Build(BuildInfo buildInfo)
|
||||||
{
|
{
|
||||||
if (BuildsInProgress.Contains(buildInfo))
|
if (_buildInProgress != null)
|
||||||
throw new InvalidOperationException("A build is already in progress");
|
throw new InvalidOperationException("A build is already in progress");
|
||||||
|
|
||||||
BuildsInProgress.Add(buildInfo);
|
_buildInProgress = buildInfo;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
|
BuildStarted?.Invoke(buildInfo);
|
||||||
buildTab.OnBuildStart();
|
|
||||||
|
|
||||||
// Required in order to update the build tasks list
|
// Required in order to update the build tasks list
|
||||||
Internal.GodotMainIteration();
|
Internal.GodotMainIteration();
|
||||||
|
@ -80,44 +87,44 @@ namespace GodotTools
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
|
BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
|
||||||
Console.Error.WriteLine(e);
|
Console.Error.WriteLine(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int exitCode = BuildSystem.Build(buildInfo);
|
int exitCode = BuildSystem.Build(buildInfo, StdOutputReceived, StdErrorReceived);
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
|
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;
|
return exitCode == 0;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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);
|
Console.Error.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
BuildsInProgress.Remove(buildInfo);
|
_buildInProgress = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<bool> BuildAsync(BuildInfo buildInfo)
|
public static async Task<bool> BuildAsync(BuildInfo buildInfo)
|
||||||
{
|
{
|
||||||
if (BuildsInProgress.Contains(buildInfo))
|
if (_buildInProgress != null)
|
||||||
throw new InvalidOperationException("A build is already in progress");
|
throw new InvalidOperationException("A build is already in progress");
|
||||||
|
|
||||||
BuildsInProgress.Add(buildInfo);
|
_buildInProgress = buildInfo;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
|
BuildStarted?.Invoke(buildInfo);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -125,43 +132,57 @@ namespace GodotTools
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
|
BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
|
||||||
Console.Error.WriteLine(e);
|
Console.Error.WriteLine(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int exitCode = await BuildSystem.BuildAsync(buildInfo);
|
int exitCode = await BuildSystem.BuildAsync(buildInfo, StdOutputReceived, StdErrorReceived);
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
|
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;
|
return exitCode == 0;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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);
|
Console.Error.WriteLine(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
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
|
return true; // No solution to build
|
||||||
|
|
||||||
// Make sure the API assemblies are up to date before building the project.
|
// 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
|
// 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.
|
// 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))
|
if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
|
||||||
{
|
{
|
||||||
|
@ -173,15 +194,6 @@ namespace GodotTools
|
||||||
{
|
{
|
||||||
pr.Step("Building project solution", 0);
|
pr.Step("Building project solution", 0);
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
if (!Build(buildInfo))
|
if (!Build(buildInfo))
|
||||||
{
|
{
|
||||||
ShowBuildErrorDialog("Failed to build project solution");
|
ShowBuildErrorDialog("Failed to build project solution");
|
||||||
|
@ -197,13 +209,7 @@ namespace GodotTools
|
||||||
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
||||||
return true; // No solution to build
|
return true; // No solution to build
|
||||||
|
|
||||||
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
|
GenerateEditorScriptMetadata();
|
||||||
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
|
|
||||||
|
|
||||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
|
|
||||||
|
|
||||||
if (File.Exists(editorScriptsMetadataPath))
|
|
||||||
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
|
|
||||||
|
|
||||||
if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
|
if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
|
||||||
return true; // Requested play from an external editor/IDE which already built the project
|
return true; // Requested play from an external editor/IDE which already built the project
|
||||||
|
@ -211,6 +217,35 @@ namespace GodotTools
|
||||||
return BuildProjectBlocking("Debug");
|
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))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new IOException("Failed to copy scripts metadata file.", innerException: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
// Build tool settings
|
// Build tool settings
|
||||||
|
@ -254,8 +289,6 @@ namespace GodotTools
|
||||||
["hint"] = Godot.PropertyHint.Enum,
|
["hint"] = Godot.PropertyHint.Enum,
|
||||||
["hint_string"] = hintString
|
["hint_string"] = hintString
|
||||||
});
|
});
|
||||||
|
|
||||||
EditorDef("mono/builds/print_build_output", false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,16 +5,10 @@ using GodotTools.Internals;
|
||||||
using File = GodotTools.Utils.File;
|
using File = GodotTools.Utils.File;
|
||||||
using Path = System.IO.Path;
|
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]
|
[Serializable]
|
||||||
private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
|
private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
|
||||||
{
|
{
|
||||||
|
@ -29,10 +23,14 @@ namespace GodotTools
|
||||||
|
|
||||||
private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
|
private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
|
||||||
private ItemList issuesList;
|
private ItemList issuesList;
|
||||||
|
private TextEdit buildLog;
|
||||||
|
private PopupMenu issuesListContextMenu;
|
||||||
|
|
||||||
public bool BuildExited { get; private set; } = false;
|
[Signal] public event Action 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;
|
public int ErrorCount { get; private set; } = 0;
|
||||||
|
|
||||||
|
@ -41,23 +39,31 @@ namespace GodotTools
|
||||||
public bool ErrorsVisible { get; set; } = true;
|
public bool ErrorsVisible { get; set; } = true;
|
||||||
public bool WarningsVisible { get; set; } = true;
|
public bool WarningsVisible { get; set; } = true;
|
||||||
|
|
||||||
public Texture2D IconTexture
|
public Texture2D BuildStateIcon
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!BuildExited)
|
if (!HasBuildExited)
|
||||||
return GetThemeIcon("Stop", "EditorIcons");
|
return GetThemeIcon("Stop", "EditorIcons");
|
||||||
|
|
||||||
if (BuildResult == BuildResults.Error)
|
if (BuildResult == Build.BuildResult.Error)
|
||||||
return GetThemeIcon("StatusError", "EditorIcons");
|
return GetThemeIcon("Error", "EditorIcons");
|
||||||
|
|
||||||
return GetThemeIcon("StatusSuccess", "EditorIcons");
|
if (WarningCount > 1)
|
||||||
|
return GetThemeIcon("Warning", "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())
|
using (var file = new Godot.File())
|
||||||
{
|
{
|
||||||
|
@ -107,7 +113,7 @@ namespace GodotTools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _IssueActivated(int idx)
|
private void IssueActivated(int idx)
|
||||||
{
|
{
|
||||||
if (idx < 0 || idx >= issuesList.GetItemCount())
|
if (idx < 0 || idx >= issuesList.GetItemCount())
|
||||||
throw new IndexOutOfRangeException("Item list index out of range");
|
throw new IndexOutOfRangeException("Item list index out of range");
|
||||||
|
@ -190,49 +196,79 @@ namespace GodotTools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnBuildStart()
|
private void BuildLaunchFailed(BuildInfo buildInfo, string cause)
|
||||||
{
|
{
|
||||||
BuildExited = false;
|
HasBuildExited = true;
|
||||||
|
BuildResult = Build.BuildResult.Error;
|
||||||
issues.Clear();
|
|
||||||
WarningCount = 0;
|
|
||||||
ErrorCount = 0;
|
|
||||||
UpdateIssuesList();
|
|
||||||
|
|
||||||
GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnBuildExit(BuildResults result)
|
|
||||||
{
|
|
||||||
BuildExited = true;
|
|
||||||
BuildResult = result;
|
|
||||||
|
|
||||||
_LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
|
|
||||||
UpdateIssuesList();
|
|
||||||
|
|
||||||
GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnBuildExecFailed(string cause)
|
|
||||||
{
|
|
||||||
BuildExited = true;
|
|
||||||
BuildResult = BuildResults.Error;
|
|
||||||
|
|
||||||
issuesList.Clear();
|
issuesList.Clear();
|
||||||
|
|
||||||
var issue = new BuildIssue { Message = cause, Warning = false };
|
var issue = new BuildIssue {Message = cause, Warning = false};
|
||||||
|
|
||||||
ErrorCount += 1;
|
ErrorCount += 1;
|
||||||
issues.Add(issue);
|
issues.Add(issue);
|
||||||
|
|
||||||
UpdateIssuesList();
|
UpdateIssuesList();
|
||||||
|
|
||||||
GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
|
EmitSignal(nameof(BuildStateChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildStarted(BuildInfo buildInfo)
|
||||||
|
{
|
||||||
|
BuildInfo = buildInfo;
|
||||||
|
HasBuildExited = false;
|
||||||
|
|
||||||
|
issues.Clear();
|
||||||
|
WarningCount = 0;
|
||||||
|
ErrorCount = 0;
|
||||||
|
buildLog.Text = string.Empty;
|
||||||
|
|
||||||
|
UpdateIssuesList();
|
||||||
|
|
||||||
|
EmitSignal(nameof(BuildStateChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildFinished(BuildResult result)
|
||||||
|
{
|
||||||
|
HasBuildExited = true;
|
||||||
|
BuildResult = result;
|
||||||
|
|
||||||
|
LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
|
||||||
|
|
||||||
|
UpdateIssuesList();
|
||||||
|
|
||||||
|
EmitSignal(nameof(BuildStateChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StdOutputReceived(string text)
|
||||||
|
{
|
||||||
|
buildLog.Text += text + "\n";
|
||||||
|
ScrollToLastNonEmptyLogLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StdErrorReceived(string text)
|
||||||
|
{
|
||||||
|
buildLog.Text += text + "\n";
|
||||||
|
ScrollToLastNonEmptyLogLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScrollToLastNonEmptyLogLine()
|
||||||
|
{
|
||||||
|
int line;
|
||||||
|
for (line = buildLog.GetLineCount(); line > 0; line--)
|
||||||
|
{
|
||||||
|
string lineText = buildLog.GetLine(line);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim()))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildLog.CursorSetLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestartBuild()
|
public void RestartBuild()
|
||||||
{
|
{
|
||||||
if (!BuildExited)
|
if (!HasBuildExited)
|
||||||
throw new InvalidOperationException("Build already started");
|
throw new InvalidOperationException("Build already started");
|
||||||
|
|
||||||
BuildManager.RestartBuild(this);
|
BuildManager.RestartBuild(this);
|
||||||
|
@ -240,28 +276,118 @@ namespace GodotTools
|
||||||
|
|
||||||
public void StopBuild()
|
public void StopBuild()
|
||||||
{
|
{
|
||||||
if (!BuildExited)
|
if (!HasBuildExited)
|
||||||
throw new InvalidOperationException("Build is not in progress");
|
throw new InvalidOperationException("Build is not in progress");
|
||||||
|
|
||||||
BuildManager.StopBuild(this);
|
BuildManager.StopBuild(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum IssuesContextMenuOption
|
||||||
|
{
|
||||||
|
Copy
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IssuesListContextOptionPressed(int id)
|
||||||
|
{
|
||||||
|
switch ((IssuesContextMenuOption)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)
|
||||||
|
DisplayServer.ClipboardSet(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.Size = new Vector2i(1, 1);
|
||||||
|
|
||||||
|
if (issuesList.IsAnythingSelected())
|
||||||
|
{
|
||||||
|
// Add menu entries for the selected item
|
||||||
|
issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"),
|
||||||
|
label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issuesListContextMenu.GetItemCount() > 0)
|
||||||
|
{
|
||||||
|
issuesListContextMenu.Position = (Vector2i)(issuesList.RectGlobalPosition + atPosition);
|
||||||
|
issuesListContextMenu.Popup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
base._Ready();
|
base._Ready();
|
||||||
|
|
||||||
issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill };
|
SizeFlagsVertical = (int)SizeFlags.ExpandFill;
|
||||||
issuesList.ItemActivated += _IssueActivated;
|
|
||||||
AddChild(issuesList);
|
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.ItemActivated += IssueActivated;
|
||||||
|
issuesList.AllowRmbSelect = true;
|
||||||
|
issuesList.ItemRmbSelected += IssuesListRmbSelected;
|
||||||
|
hsc.AddChild(issuesList);
|
||||||
|
|
||||||
|
issuesListContextMenu = new PopupMenu();
|
||||||
|
issuesListContextMenu.IdPressed += 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 void OnBeforeSerialize()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public BuildTab(BuildInfo buildInfo)
|
public void OnAfterDeserialize()
|
||||||
{
|
{
|
||||||
BuildInfo = buildInfo;
|
AddBuildEventListeners(); // Re-add them
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace GodotTools.Build
|
||||||
|
{
|
||||||
|
public enum BuildResult
|
||||||
|
{
|
||||||
|
Error,
|
||||||
|
Success
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,10 +44,7 @@ namespace GodotTools.Build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PrintBuildOutput =>
|
private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
|
||||||
(bool)EditorSettings.GetSetting("mono/builds/print_build_output");
|
|
||||||
|
|
||||||
private static Process LaunchBuild(BuildInfo buildInfo)
|
|
||||||
{
|
{
|
||||||
(string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
|
(string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
|
||||||
|
|
||||||
|
@ -58,13 +55,13 @@ namespace GodotTools.Build
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
|
var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
|
||||||
|
|
||||||
bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
|
string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}";
|
||||||
|
stdOutHandler?.Invoke(launchMessage);
|
||||||
|
if (Godot.OS.IsStdoutVerbose())
|
||||||
|
Console.WriteLine(launchMessage);
|
||||||
|
|
||||||
if (!redirectOutput || Godot.OS.IsStdoutVerbose())
|
startInfo.RedirectStandardOutput = true;
|
||||||
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
|
startInfo.RedirectStandardError = true;
|
||||||
|
|
||||||
startInfo.RedirectStandardOutput = redirectOutput;
|
|
||||||
startInfo.RedirectStandardError = redirectOutput;
|
|
||||||
startInfo.UseShellExecute = false;
|
startInfo.UseShellExecute = false;
|
||||||
|
|
||||||
if (UsingMonoMsBuildOnWindows)
|
if (UsingMonoMsBuildOnWindows)
|
||||||
|
@ -82,20 +79,22 @@ namespace GodotTools.Build
|
||||||
|
|
||||||
var process = new Process {StartInfo = startInfo};
|
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();
|
process.Start();
|
||||||
|
|
||||||
if (redirectOutput)
|
|
||||||
{
|
|
||||||
process.BeginOutputReadLine();
|
process.BeginOutputReadLine();
|
||||||
process.BeginErrorReadLine();
|
process.BeginErrorReadLine();
|
||||||
}
|
|
||||||
|
|
||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int Build(BuildInfo buildInfo)
|
public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
|
||||||
{
|
{
|
||||||
using (var process = LaunchBuild(buildInfo))
|
using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
|
||||||
{
|
{
|
||||||
process.WaitForExit();
|
process.WaitForExit();
|
||||||
|
|
||||||
|
@ -103,9 +102,9 @@ namespace GodotTools.Build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<int> BuildAsync(BuildInfo buildInfo)
|
public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
|
||||||
{
|
{
|
||||||
using (var process = LaunchBuild(buildInfo))
|
using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
|
||||||
{
|
{
|
||||||
await process.WaitForExitAsync();
|
await process.WaitForExitAsync();
|
||||||
|
|
||||||
|
@ -152,10 +151,5 @@ namespace GodotTools.Build
|
||||||
foreach (string env in platformEnvironmentVariables)
|
foreach (string env in platformEnvironmentVariables)
|
||||||
environmentVariables.Remove(env);
|
environmentVariables.Remove(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsDebugMsBuildRequested()
|
|
||||||
{
|
|
||||||
return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
165
modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
Normal file
165
modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
Normal file
|
@ -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.EditorDebuggerNodeReloadScripts();
|
||||||
|
|
||||||
|
// Hot-reload in the editor
|
||||||
|
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("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.EditorDebuggerNodeReloadScripts();
|
||||||
|
|
||||||
|
// Hot-reload in the editor
|
||||||
|
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("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(int id)
|
||||||
|
{
|
||||||
|
switch ((BuildMenuOptions)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 = GetThemeIcon("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.IdPressed += BuildMenuOptionPressed;
|
||||||
|
|
||||||
|
errorsBtn = new Button
|
||||||
|
{
|
||||||
|
HintTooltip = "Show Errors".TTR(),
|
||||||
|
Icon = GetThemeIcon("StatusError", "EditorIcons"),
|
||||||
|
ExpandIcon = false,
|
||||||
|
ToggleMode = true,
|
||||||
|
Pressed = true,
|
||||||
|
FocusMode = FocusModeEnum.None
|
||||||
|
};
|
||||||
|
errorsBtn.Toggled += ErrorsToggled;
|
||||||
|
toolBarHBox.AddChild(errorsBtn);
|
||||||
|
|
||||||
|
warningsBtn = new Button
|
||||||
|
{
|
||||||
|
HintTooltip = "Show Warnings".TTR(),
|
||||||
|
Icon = GetThemeIcon("NodeWarning", "EditorIcons"),
|
||||||
|
ExpandIcon = false,
|
||||||
|
ToggleMode = true,
|
||||||
|
Pressed = true,
|
||||||
|
FocusMode = FocusModeEnum.None
|
||||||
|
};
|
||||||
|
warningsBtn.Toggled += WarningsToggled;
|
||||||
|
toolBarHBox.AddChild(warningsBtn);
|
||||||
|
|
||||||
|
viewLogBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Show Output".TTR(),
|
||||||
|
ToggleMode = true,
|
||||||
|
Pressed = true,
|
||||||
|
FocusMode = FocusModeEnum.None
|
||||||
|
};
|
||||||
|
viewLogBtn.Toggled += ViewLogToggled;
|
||||||
|
toolBarHBox.AddChild(viewLogBtn);
|
||||||
|
|
||||||
|
BuildOutputView = new BuildOutputView();
|
||||||
|
AddChild(BuildOutputView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ namespace GodotTools.Build
|
||||||
string dotnetCliPath = OS.PathWhich("dotnet");
|
string dotnetCliPath = OS.PathWhich("dotnet");
|
||||||
if (!string.IsNullOrEmpty(dotnetCliPath))
|
if (!string.IsNullOrEmpty(dotnetCliPath))
|
||||||
return (dotnetCliPath, BuildTool.DotnetCli);
|
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;
|
goto case BuildTool.MsBuildVs;
|
||||||
}
|
}
|
||||||
case BuildTool.MsBuildVs:
|
case BuildTool.MsBuildVs:
|
||||||
|
@ -89,7 +89,7 @@ namespace GodotTools.Build
|
||||||
string dotnetCliPath = OS.PathWhich("dotnet");
|
string dotnetCliPath = OS.PathWhich("dotnet");
|
||||||
if (!string.IsNullOrEmpty(dotnetCliPath))
|
if (!string.IsNullOrEmpty(dotnetCliPath))
|
||||||
return (dotnetCliPath, BuildTool.DotnetCli);
|
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;
|
goto case BuildTool.MsBuildMono;
|
||||||
}
|
}
|
||||||
case BuildTool.MsBuildMono:
|
case BuildTool.MsBuildMono:
|
||||||
|
@ -161,7 +161,7 @@ namespace GodotTools.Build
|
||||||
|
|
||||||
// Try to find 15.0 with vswhere
|
// 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;
|
string vsWherePath = null;
|
||||||
foreach (var envName in envNames)
|
foreach (var envName in envNames)
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using GodotTools.Build;
|
||||||
using GodotTools.Core;
|
using GodotTools.Core;
|
||||||
using GodotTools.Internals;
|
using GodotTools.Internals;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
@ -143,6 +144,8 @@ namespace GodotTools.Export
|
||||||
|
|
||||||
private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
|
private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
|
||||||
{
|
{
|
||||||
|
_ = flags; // Unused
|
||||||
|
|
||||||
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -154,12 +157,10 @@ namespace GodotTools.Export
|
||||||
|
|
||||||
string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
|
string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
|
||||||
|
|
||||||
string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
|
string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug);
|
||||||
CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
|
|
||||||
|
|
||||||
AddFile(scriptsMetadataPath, scriptsMetadataPath);
|
AddFile(scriptsMetadataPath, scriptsMetadataPath);
|
||||||
|
|
||||||
if (!BuildManager.BuildProjectBlocking(buildConfig, platform))
|
if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform))
|
||||||
throw new Exception("Failed to build project");
|
throw new Exception("Failed to build project");
|
||||||
|
|
||||||
// Add dependency assemblies
|
// Add dependency assemblies
|
||||||
|
|
|
@ -4,9 +4,9 @@ using GodotTools.Export;
|
||||||
using GodotTools.Utils;
|
using GodotTools.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using GodotTools.Build;
|
||||||
using GodotTools.Ides;
|
using GodotTools.Ides;
|
||||||
using GodotTools.Ides.Rider;
|
using GodotTools.Ides.Rider;
|
||||||
using GodotTools.Internals;
|
using GodotTools.Internals;
|
||||||
|
@ -19,7 +19,6 @@ using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace GodotTools
|
namespace GodotTools
|
||||||
{
|
{
|
||||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
|
||||||
public class GodotSharpEditor : EditorPlugin, ISerializationListener
|
public class GodotSharpEditor : EditorPlugin, ISerializationListener
|
||||||
{
|
{
|
||||||
private EditorSettings editorSettings;
|
private EditorSettings editorSettings;
|
||||||
|
@ -37,7 +36,7 @@ namespace GodotTools
|
||||||
|
|
||||||
private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization
|
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;
|
public bool SkipBuildBeforePlaying { get; set; } = false;
|
||||||
|
|
||||||
|
@ -153,7 +152,7 @@ namespace GodotTools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _BuildSolutionPressed()
|
private void BuildSolutionPressed()
|
||||||
{
|
{
|
||||||
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
|
||||||
{
|
{
|
||||||
|
@ -161,15 +160,15 @@ namespace GodotTools
|
||||||
return; // Failed to create solution
|
return; // Failed to create solution
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance.BottomPanel.BuildProjectPressed();
|
Instance.MSBuildPanel.BuildSolution();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Notification(int what)
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
base._Notification(what);
|
base._Ready();
|
||||||
|
|
||||||
|
MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
|
||||||
|
|
||||||
if (what == NotificationReady)
|
|
||||||
{
|
|
||||||
bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
|
bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
|
||||||
if (showInfoDialog)
|
if (showInfoDialog)
|
||||||
{
|
{
|
||||||
|
@ -179,7 +178,6 @@ namespace GodotTools
|
||||||
aboutDialog.Exclusive = false;
|
aboutDialog.Exclusive = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private enum MenuOptions
|
private enum MenuOptions
|
||||||
{
|
{
|
||||||
|
@ -393,6 +391,12 @@ namespace GodotTools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildStateChanged()
|
||||||
|
{
|
||||||
|
if (bottomPanelBtn != null)
|
||||||
|
bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon;
|
||||||
|
}
|
||||||
|
|
||||||
public override void EnablePlugin()
|
public override void EnablePlugin()
|
||||||
{
|
{
|
||||||
base.EnablePlugin();
|
base.EnablePlugin();
|
||||||
|
@ -409,16 +413,15 @@ namespace GodotTools
|
||||||
errorDialog = new AcceptDialog();
|
errorDialog = new AcceptDialog();
|
||||||
editorBaseControl.AddChild(errorDialog);
|
editorBaseControl.AddChild(errorDialog);
|
||||||
|
|
||||||
BottomPanel = new BottomPanel();
|
MSBuildPanel = new MSBuildPanel();
|
||||||
|
bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR());
|
||||||
bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR());
|
|
||||||
|
|
||||||
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
|
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
|
||||||
|
|
||||||
menuPopup = new PopupMenu();
|
menuPopup = new PopupMenu();
|
||||||
menuPopup.Hide();
|
menuPopup.Hide();
|
||||||
|
|
||||||
AddToolSubmenuItem("Mono", menuPopup);
|
AddToolSubmenuItem("C#", menuPopup);
|
||||||
|
|
||||||
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
|
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
|
||||||
{
|
{
|
||||||
|
@ -476,7 +479,7 @@ namespace GodotTools
|
||||||
HintTooltip = "Build solution",
|
HintTooltip = "Build solution",
|
||||||
FocusMode = Control.FocusModeEnum.None
|
FocusMode = Control.FocusModeEnum.None
|
||||||
};
|
};
|
||||||
toolBarBuildButton.PressedSignal += _BuildSolutionPressed;
|
toolBarBuildButton.PressedSignal += BuildSolutionPressed;
|
||||||
AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton);
|
AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton);
|
||||||
|
|
||||||
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
|
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
|
||||||
|
@ -570,6 +573,7 @@ namespace GodotTools
|
||||||
|
|
||||||
public static GodotSharpEditor Instance { get; private set; }
|
public static GodotSharpEditor Instance { get; private set; }
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
private GodotSharpEditor()
|
private GodotSharpEditor()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue