ソースを参照

Merge pull request #38650 from neikeq/dotnet-cli-support

C#: Support for building with the dotnet CLI
Rémi Verschelde 5 年 前
コミット
54bb4cb6d6

+ 1 - 1
modules/mono/build_scripts/godot_tools_build.py

@@ -15,7 +15,7 @@ def build_godot_tools(source, target, env):
 
     from .solution_builder import build_solution
 
-    build_solution(env, solution_path, build_config, restore=True)
+    build_solution(env, solution_path, build_config)
     # No need to copy targets. The GodotTools csproj takes care of copying them.
 
 

+ 44 - 24
modules/mono/build_scripts/solution_builder.py

@@ -4,7 +4,29 @@ import os
 verbose = False
 
 
-def find_msbuild_unix(filename):
+def find_dotnet_cli():
+    import os.path
+
+    if os.name == "nt":
+        windows_exts = os.environ["PATHEXT"]
+        windows_exts = windows_exts.split(os.pathsep) if windows_exts else []
+
+        for hint_dir in os.environ["PATH"].split(os.pathsep):
+            hint_dir = hint_dir.strip('"')
+            hint_path = os.path.join(hint_dir, "dotnet")
+            if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+                return hint_path
+            if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
+                return hint_path + ".exe"
+    else:
+        for hint_dir in os.environ["PATH"].split(os.pathsep):
+            hint_dir = hint_dir.strip('"')
+            hint_path = os.path.join(hint_dir, "dotnet")
+            if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+                return hint_path
+
+
+def find_msbuild_unix():
     import os.path
     import sys
 
@@ -86,15 +108,7 @@ def run_command(command, args, env_override=None, name=None):
         raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode))
 
 
-def nuget_restore(env, *args):
-    global verbose
-    verbose = env["verbose"]
-
-    # Do NuGet restore
-    run_command(nuget_path, ["restore"] + list(args), name="nuget restore")
-
-
-def build_solution(env, solution_path, build_config, extra_msbuild_args=[], restore=False):
+def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
     global verbose
     verbose = env["verbose"]
 
@@ -104,27 +118,33 @@ def build_solution(env, solution_path, build_config, extra_msbuild_args=[], rest
     if "PLATFORM" in msbuild_env:
         del msbuild_env["PLATFORM"]
 
-    # Find MSBuild
-    if os.name == "nt":
-        msbuild_info = find_msbuild_windows(env)
-        if msbuild_info is None:
-            raise RuntimeError("Cannot find MSBuild executable")
-        msbuild_path = msbuild_info[0]
-        msbuild_env.update(msbuild_info[1])
+    msbuild_args = []
+
+    dotnet_cli = find_dotnet_cli()
+
+    if dotnet_cli:
+        msbuild_path = dotnet_cli
+        msbuild_args += ["msbuild"]  # `dotnet msbuild` command
     else:
-        msbuild_path = find_msbuild_unix("msbuild")
-        if msbuild_path is None:
-            raise RuntimeError("Cannot find MSBuild executable")
+        # Find MSBuild
+        if os.name == "nt":
+            msbuild_info = find_msbuild_windows(env)
+            if msbuild_info is None:
+                raise RuntimeError("Cannot find MSBuild executable")
+            msbuild_path = msbuild_info[0]
+            msbuild_env.update(msbuild_info[1])
+        else:
+            msbuild_path = find_msbuild_unix()
+            if msbuild_path is None:
+                raise RuntimeError("Cannot find MSBuild executable")
 
     print("MSBuild path: " + msbuild_path)
 
     # Build solution
 
-    targets = ["Build"]
-    if restore:
-        targets.insert(0, "Restore")
+    targets = ["Restore", "Build"]
 
-    msbuild_args = [solution_path, "/t:%s" % ",".join(targets), "/p:Configuration=" + build_config]
+    msbuild_args += [solution_path, "/t:%s" % ",".join(targets), "/p:Configuration=" + build_config]
     msbuild_args += extra_msbuild_args
 
     run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild")

+ 8 - 8
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj

@@ -6,18 +6,18 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.Build" Version="16.5.0" />
-    <PackageReference Include="Microsoft.Build.Runtime" Version="16.5.0" />
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
   </ItemGroup>
-  <PropertyGroup>
+  <ItemGroup>
     <!--
-    The 'Microsoft.Build.Runtime' package includes an mscorlib reference assembly in contentFiles.
-    This causes our project build to fail. As a workaround, we remove {CandidateAssemblyFiles}
-    from AssemblySearchPaths as described here: https://github.com/microsoft/msbuild/issues/3486.
+    The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
+    here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
+    We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
+    searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
     -->
-    <AssemblySearchPaths>$([System.String]::Copy('$(AssemblySearchPaths)').Replace('{CandidateAssemblyFiles}', ''))</AssemblySearchPaths>
-    <AssemblySearchPaths Condition=" '$(MSBuildRuntimeVersion)' != '' ">$(AssemblySearchPaths.Split(';'))</AssemblySearchPaths>
-  </PropertyGroup>
+    <None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
+  </ItemGroup>
 </Project>

+ 0 - 0
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe


+ 6 - 0
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs

@@ -125,6 +125,12 @@ namespace GodotTools.ProjectEditor
             // References
             var referenceGroup = root.AddItemGroup();
             referenceGroup.AddItem("Reference", "System");
+            var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
+
+            // Use metadata (child nodes) instead of attributes for the PackageReference.
+            // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
+            frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
+            frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
 
             root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
 

+ 23 - 3
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs

@@ -173,7 +173,7 @@ namespace GodotTools.ProjectEditor
             void AddPropertyIfNotPresent(string name, string condition, string value)
             {
                 if (root.PropertyGroups
-                    .Any(g => (g.Condition == string.Empty || g.Condition.Trim() == condition) &&
+                    .Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) &&
                               g.Properties
                                   .Any(p => p.Name == name &&
                                             p.Value == value &&
@@ -264,7 +264,7 @@ namespace GodotTools.ProjectEditor
             bool hasGodotProjectGeneratorVersion = false;
             bool foundOldConfiguration = false;
 
-            foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition == string.Empty))
+            foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition)))
             {
                 if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion"))
                     hasGodotProjectGeneratorVersion = true;
@@ -280,7 +280,7 @@ namespace GodotTools.ProjectEditor
 
             if (!hasGodotProjectGeneratorVersion)
             {
-                root.PropertyGroups.First(g => g.Condition == string.Empty)?
+                root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))?
                     .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
                 project.HasUnsavedChanges = true;
             }
@@ -348,5 +348,25 @@ namespace GodotTools.ProjectEditor
                 MigrateConfigurationConditions("Tools", "Debug"); // Must be last
             }
         }
+
+        public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project)
+        {
+            var root = project.Root;
+
+            bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any(
+                item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies"));
+
+            if (found)
+                return;
+
+            var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
+
+            // Use metadata (child nodes) instead of attributes for the PackageReference.
+            // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
+            frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
+            frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
+
+            project.HasUnsavedChanges = true;
+        }
     }
 }

+ 25 - 25
modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs

@@ -14,16 +14,6 @@ namespace GodotTools.Build
 {
     public static class BuildSystem
     {
-        private static string GetMsBuildPath()
-        {
-            string msbuildPath = MsBuildFinder.FindMsBuild();
-
-            if (msbuildPath == null)
-                throw new FileNotFoundException("Cannot find the MSBuild executable.");
-
-            return msbuildPath;
-        }
-
         private static string MonoWindowsBinDir
         {
             get
@@ -46,8 +36,8 @@ namespace GodotTools.Build
             {
                 if (OS.IsWindows)
                 {
-                    return (BuildManager.BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
-                           == BuildManager.BuildTool.MsBuildMono;
+                    return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
+                           == BuildTool.MsBuildMono;
                 }
 
                 return false;
@@ -57,16 +47,21 @@ namespace GodotTools.Build
         private static bool PrintBuildOutput =>
             (bool)EditorSettings.GetSetting("mono/builds/print_build_output");
 
-        private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+        private static Process LaunchBuild(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
         {
+            (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
+
+            if (msbuildPath == null)
+                throw new FileNotFoundException("Cannot find the MSBuild executable.");
+
             var customPropertiesList = new List<string>();
 
             if (customProperties != null)
                 customPropertiesList.AddRange(customProperties);
 
-            string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
+            string compilerArgs = BuildArguments(buildTool, solution, targets, config, loggerOutputDir, customPropertiesList);
 
-            var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
+            var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
 
             bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
 
@@ -90,7 +85,7 @@ namespace GodotTools.Build
             // Needed when running from Developer Command Prompt for VS
             RemovePlatformVariable(startInfo.EnvironmentVariables);
 
-            var process = new Process { StartInfo = startInfo };
+            var process = new Process {StartInfo = startInfo};
 
             process.Start();
 
@@ -105,19 +100,19 @@ namespace GodotTools.Build
 
         public static int Build(BuildInfo buildInfo)
         {
-            return Build(buildInfo.Solution, buildInfo.Configuration,
+            return Build(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration,
                 buildInfo.LogsDirPath, buildInfo.CustomProperties);
         }
 
         public static Task<int> BuildAsync(BuildInfo buildInfo)
         {
-            return BuildAsync(buildInfo.Solution, buildInfo.Configuration,
+            return BuildAsync(buildInfo.Solution, buildInfo.Targets, buildInfo.Configuration,
                 buildInfo.LogsDirPath, buildInfo.CustomProperties);
         }
 
-        public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+        public static int Build(string solution, string[] targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
         {
-            using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+            using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties))
             {
                 process.WaitForExit();
 
@@ -125,9 +120,9 @@ namespace GodotTools.Build
             }
         }
 
-        public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+        public static async Task<int> BuildAsync(string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
         {
-            using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+            using (var process = LaunchBuild(solution, targets, config, loggerOutputDir, customProperties))
             {
                 await process.WaitForExitAsync();
 
@@ -135,10 +130,15 @@ namespace GodotTools.Build
             }
         }
 
-        private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties)
+        private static string BuildArguments(BuildTool buildTool, string solution, IEnumerable<string> targets, string config, string loggerOutputDir, IEnumerable<string> customProperties)
         {
-            string arguments = $@"""{solution}"" /v:normal /t:Build ""/p:{"Configuration=" + config}"" " +
-                               $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
+            string arguments = string.Empty;
+
+            if (buildTool == BuildTool.DotnetCli)
+                arguments += "msbuild "; // `dotnet msbuild` command
+
+            arguments += $@"""{solution}"" /v:normal /t:{string.Join(",", targets)} ""/p:{"Configuration=" + config}"" " +
+                         $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
 
             foreach (string customProperty in customProperties)
             {

+ 10 - 0
modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs

@@ -0,0 +1,10 @@
+namespace GodotTools.Build
+{
+    public enum BuildTool
+    {
+        MsBuildMono,
+        MsBuildVs,
+        JetBrainsMsBuild,
+        DotnetCli
+    }
+}

+ 36 - 17
modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs

@@ -17,16 +17,24 @@ namespace GodotTools.Build
         private static string _msbuildToolsPath = string.Empty;
         private static string _msbuildUnixPath = string.Empty;
 
-        public static string FindMsBuild()
+        public static (string, BuildTool) FindMsBuild()
         {
             var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
-            var buildTool = (BuildManager.BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
+            var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
 
             if (OS.IsWindows)
             {
                 switch (buildTool)
                 {
-                    case BuildManager.BuildTool.MsBuildVs:
+                    case BuildTool.DotnetCli:
+                    {
+                        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.");
+                        goto case BuildTool.MsBuildVs;
+                    }
+                    case BuildTool.MsBuildVs:
                     {
                         if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath))
                         {
@@ -40,18 +48,18 @@ namespace GodotTools.Build
                         if (!_msbuildToolsPath.EndsWith("\\"))
                             _msbuildToolsPath += "\\";
 
-                        return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
+                        return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs);
                     }
-                    case BuildManager.BuildTool.MsBuildMono:
+                    case BuildTool.MsBuildMono:
                     {
                         string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
 
                         if (!File.Exists(msbuildPath))
                             throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}");
 
-                        return msbuildPath;
+                        return (msbuildPath, BuildTool.MsBuildMono);
                     }
-                    case BuildManager.BuildTool.JetBrainsMsBuild:
+                    case BuildTool.JetBrainsMsBuild:
                     {
                         var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName);
 
@@ -65,7 +73,7 @@ namespace GodotTools.Build
                         if (!File.Exists(msbuildPath))
                             throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}");
 
-                        return msbuildPath;
+                        return (msbuildPath, BuildTool.JetBrainsMsBuild);
                     }
                     default:
                         throw new IndexOutOfRangeException("Invalid build tool in editor settings");
@@ -74,21 +82,32 @@ namespace GodotTools.Build
 
             if (OS.IsUnixLike)
             {
-                if (buildTool == BuildManager.BuildTool.MsBuildMono)
+                switch (buildTool)
                 {
-                    if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath))
+                    case BuildTool.DotnetCli:
                     {
-                        // Try to search it again if it wasn't found last time or if it was removed from its location
-                        _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+                        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.");
+                        goto case BuildTool.MsBuildMono;
                     }
+                    case BuildTool.MsBuildMono:
+                    {
+                        if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath))
+                        {
+                            // Try to search it again if it wasn't found last time or if it was removed from its location
+                            _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+                        }
 
-                    if (string.IsNullOrEmpty(_msbuildUnixPath))
-                        throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'");
+                        if (string.IsNullOrEmpty(_msbuildUnixPath))
+                            throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'");
 
-                    return _msbuildUnixPath;
+                        return (_msbuildUnixPath, BuildTool.MsBuildMono);
+                    }
+                    default:
+                        throw new IndexOutOfRangeException("Invalid build tool in editor settings");
                 }
-
-                throw new IndexOutOfRangeException("Invalid build tool in editor settings");
             }
 
             throw new PlatformNotSupportedException();

+ 3 - 1
modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs

@@ -10,6 +10,7 @@ namespace GodotTools
     public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
     {
         public string Solution { get; }
+        public string[] Targets { get; }
         public string Configuration { get; }
         public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization
 
@@ -38,9 +39,10 @@ namespace GodotTools
         {
         }
 
-        public BuildInfo(string solution, string configuration)
+        public BuildInfo(string solution, string[] targets, string configuration)
         {
             Solution = solution;
+            Targets = targets;
             Configuration = configuration;
         }
     }

+ 36 - 16
modules/mono/editor/GodotTools/GodotTools/BuildManager.cs

@@ -18,17 +18,11 @@ namespace GodotTools
         public const string PropNameMSBuildMono = "MSBuild (Mono)";
         public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)";
         public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)";
+        public const string PropNameDotnetCli = "dotnet CLI";
 
         public const string MsBuildIssuesFileName = "msbuild_issues.csv";
         public const string MsBuildLogFileName = "msbuild_log.txt";
 
-        public enum BuildTool
-        {
-            MsBuildMono,
-            MsBuildVs,
-            JetBrainsMsBuild
-        }
-
         private static void RemoveOldIssuesFile(BuildInfo buildInfo)
         {
             var issuesFile = GetIssuesFilePath(buildInfo);
@@ -181,10 +175,12 @@ namespace GodotTools
             {
                 pr.Step("Building project solution", 0);
 
-                var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config);
+                var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Restore", "Build"}, config);
+
+                bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli;
 
                 // Add Godot defines
-                string constants = buildTool != BuildTool.MsBuildMono ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
+                string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
 
                 foreach (var godotDefine in godotDefines)
                     constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
@@ -192,7 +188,7 @@ namespace GodotTools
                 if (Internal.GodotIsRealTDouble())
                     constants += "GODOT_REAL_T_IS_DOUBLE;";
 
-                constants += buildTool != BuildTool.MsBuildMono ? "\"" : "\\\"";
+                constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\"";
 
                 buildInfo.CustomProperties.Add(constants);
 
@@ -250,20 +246,44 @@ namespace GodotTools
         {
             // Build tool settings
             var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
-            var msbuild = BuildTool.MsBuildMono;
+
+            BuildTool msbuildDefault;
+
             if (OS.IsWindows)
-                msbuild = RiderPathManager.IsExternalEditorSetToRider(editorSettings) ? BuildTool.JetBrainsMsBuild : BuildTool.MsBuildVs;
+            {
+                if (RiderPathManager.IsExternalEditorSetToRider(editorSettings))
+                    msbuildDefault = BuildTool.JetBrainsMsBuild;
+                else
+                    msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs;
+            }
+            else
+            {
+                msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono;
+            }
+
+            EditorDef("mono/builds/build_tool", msbuildDefault);
 
-            EditorDef("mono/builds/build_tool", msbuild);
+            string hintString;
+
+            if (OS.IsWindows)
+            {
+                hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
+                             $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," +
+                             $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," +
+                             $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
+            }
+            else
+            {
+                hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
+                             $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
+            }
 
             editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
             {
                 ["type"] = Godot.Variant.Type.Int,
                 ["name"] = "mono/builds/build_tool",
                 ["hint"] = Godot.PropertyHint.Enum,
-                ["hint_string"] = OS.IsWindows ?
-                    $"{PropNameMSBuildMono},{PropNameMSBuildVs},{PropNameMSBuildJetBrains}" :
-                    $"{PropNameMSBuildMono}"
+                ["hint_string"] = hintString
             });
 
             EditorDef("mono/builds/print_build_output", false);

+ 3 - 0
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -461,6 +461,9 @@ namespace GodotTools
                     // Make sure the existing project has Api assembly references configured correctly
                     ProjectUtils.FixApiHintPath(msbuildProject);
 
+                    // Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package
+                    ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject);
+
                     if (msbuildProject.HasUnsavedChanges)
                     {
                         // Save a copy of the project before replacing it

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj

@@ -17,6 +17,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <Reference Include="GodotSharp">
       <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>

+ 1 - 0
modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj

@@ -30,6 +30,7 @@
     <ConsolePause>false</ConsolePause>
   </PropertyGroup>
   <ItemGroup>
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
     <Reference Include="System" />
   </ItemGroup>
   <ItemGroup>

+ 1 - 0
modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj

@@ -30,6 +30,7 @@
     <ConsolePause>false</ConsolePause>
   </PropertyGroup>
   <ItemGroup>
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
     <Reference Include="System" />
   </ItemGroup>
   <ItemGroup>