diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b61ea766dfe..d9908ae9301 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -105,7 +105,6 @@ jobs: - name: Test run: dotnet test Tools/MonoGame.Tools.Tests/MonoGame.Tools.Tests.csproj --blame-hang-timeout 5m -c Release --filter="TestCategory!=Audio" env: - DOTNET_ROOT: ${{github.workspace}}/dotnet64 MGFXC_WINE_PATH: /Users/runner/.winemonogame CI: true if: runner.os == 'macOS' @@ -231,11 +230,6 @@ jobs: path: tests-windowsdx if: runner.os == 'Windows' - - name: Install Tools - run: | - dotnet tool install --create-manifest-if-needed mgcb-basisu - dotnet tool install --create-manifest-if-needed mgcb-crunch - - name: Run Tools Tests run: dotnet test tests-tools/MonoGame.Tools.Tests.dll --blame-hang-timeout 1m --filter="TestCategory!=Effects" env: diff --git a/.vscode/launch.json b/.vscode/launch.json index bf6c0271172..cd99a61f5a0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "mgcb-editor-mac", - "program": "${workspaceFolder}/Artifacts/MonoGame.Content.Builder.Editor/Mac/Debug/MGCB Editor.app/Contents/MacOS/mgcb-editor-mac", + "program": "${workspaceFolder}/Artifacts/MonoGame.Content.Builder.Editor/Mac/Debug/mgcb-editor-mac.app/Contents/MacOS/mgcb-editor-mac", "args": [], "cwd": "${workspaceFolder}/Artifacts/MonoGame.Content.Builder.Editor/Mac/Debug", "console": "internalConsole", diff --git a/MonoGame.Framework.Content.Pipeline/ExternalTool.cs b/MonoGame.Framework.Content.Pipeline/ExternalTool.cs index c1ceb7b4d2c..778707cda56 100644 --- a/MonoGame.Framework.Content.Pipeline/ExternalTool.cs +++ b/MonoGame.Framework.Content.Pipeline/ExternalTool.cs @@ -5,6 +5,8 @@ using System; using System.Diagnostics; using System.IO; +using System.Net; +using System.Reflection; using System.Threading; using MonoGame.Framework.Utilities; @@ -17,6 +19,9 @@ namespace Microsoft.Xna.Framework.Content.Pipeline /// internal class ExternalTool { + public static string Crunch = "mgcb-crunch"; + public static string BasisU = "mgcb-basisu"; + public static int Run(string command, string arguments) { string stdout, stderr; @@ -27,13 +32,45 @@ public static int Run(string command, string arguments) return result; } + public static void RestoreDotnetTool(string command, string toolName) + { + string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory); + if (CurrentPlatform.OS == OS.Linux) + path= Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "linux"); + if (CurrentPlatform.OS == OS.MacOSX) + path= Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "osx"); + if (Directory.Exists (Path.Combine(path, toolName))) + return; + Directory.CreateDirectory(path); + var exe = CurrentPlatform.OS == OS.Windows ? "dotnet.exe" : "dotnet"; + var dotnetRoot = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + if (!string.IsNullOrEmpty(dotnetRoot)) + { + exe = Path.Combine(dotnetRoot, exe); + } + var version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + if (Run(exe, $"tool {command} {toolName} --version {version} --tool-path .", out string _, out string _, workingDirectory: path) != 0) + { + // install the latest + Run(exe, $"tool {command} {toolName} --tool-path .", out _, out _, workingDirectory: path); + } + } + + public static void RestoreDotnetTools() + { + RestoreDotnetTool("install", Crunch); + RestoreDotnetTool("install", BasisU); + } + /// /// Run a dotnet tool. The tool should be installed in a .config/dotnet-tools.json file somewhere in the project lineage. /// public static int RunDotnetTool(string toolName, string args, out string stdOut, out string stdErr, string stdIn=null, string workingDirectory=null) { - var finalizedArgs = toolName + " " + args; - return ExternalTool.Run("dotnet", finalizedArgs, out stdOut, out stdErr, stdIn, workingDirectory); + RestoreDotnetTools(); + var exe = FindCommand (toolName); + var finalizedArgs = args; + return ExternalTool.Run(exe, finalizedArgs, out stdOut, out stdErr, stdIn, workingDirectory); } public static int Run(string command, string arguments, out string stdout, out string stderr, string stdin = null, string workingDirectory=null) @@ -68,6 +105,11 @@ public static int Run(string command, string arguments, out string stdout, out s if (!string.IsNullOrEmpty(workingDirectory)) processInfo.WorkingDirectory = workingDirectory; + var dotnetRoot = Environment.GetEnvironmentVariable ("DOTNET_ROOT"); + if (!string.IsNullOrEmpty(dotnetRoot)) { + processInfo.EnvironmentVariables["DOTNET_ROOT"] = dotnetRoot; + } + EnsureExecutable(fullPath); using (var process = new Process()) diff --git a/MonoGame.Framework.Content.Pipeline/Utilities/BasisUHelpers.cs b/MonoGame.Framework.Content.Pipeline/Utilities/BasisUHelpers.cs index f16fbb40388..29b0bf976ae 100644 --- a/MonoGame.Framework.Content.Pipeline/Utilities/BasisUHelpers.cs +++ b/MonoGame.Framework.Content.Pipeline/Utilities/BasisUHelpers.cs @@ -206,7 +206,7 @@ internal static class BasisU /// The exit code for the basisu process. public static int Run(string args, out string stdOut, out string stdErr, string stdIn=null, string workingDirectory=null) { - return ExternalTool.RunDotnetTool("mgcb-basisu", args, out stdOut, out stdErr, stdIn, workingDirectory); + return ExternalTool.RunDotnetTool(ExternalTool.BasisU, args, out stdOut, out stdErr, stdIn, workingDirectory); } /// diff --git a/MonoGame.Framework.Content.Pipeline/Utilities/CrunchHelpers.cs b/MonoGame.Framework.Content.Pipeline/Utilities/CrunchHelpers.cs index 8ef1ae3f97a..ffdb259e810 100644 --- a/MonoGame.Framework.Content.Pipeline/Utilities/CrunchHelpers.cs +++ b/MonoGame.Framework.Content.Pipeline/Utilities/CrunchHelpers.cs @@ -137,7 +137,7 @@ internal static class Crunch /// The exit code for the basisu process. private static int Run(string args, out string stdOut, out string stdErr) { - return ExternalTool.RunDotnetTool("mgcb-crunch", args, out stdOut, out stdErr); + return ExternalTool.RunDotnetTool(ExternalTool.Crunch, args, out stdOut, out stdErr); } /// diff --git a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.Android.CSharp/.config/dotnet-tools.json b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.Android.CSharp/.config/dotnet-tools.json index a966e81a617..099bf0db52e 100644 --- a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.Android.CSharp/.config/dotnet-tools.json +++ b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.Android.CSharp/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-mgcb": { - "version": "3.8.2.1-develop", - "commands": [ - "mgcb" - ] - }, "dotnet-mgcb-editor": { "version": "3.8.2.1-develop", "commands": [ diff --git a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.DesktopGL.CSharp/.config/dotnet-tools.json b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.DesktopGL.CSharp/.config/dotnet-tools.json index a966e81a617..099bf0db52e 100644 --- a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.DesktopGL.CSharp/.config/dotnet-tools.json +++ b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.DesktopGL.CSharp/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-mgcb": { - "version": "3.8.2.1-develop", - "commands": [ - "mgcb" - ] - }, "dotnet-mgcb-editor": { "version": "3.8.2.1-develop", "commands": [ diff --git a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.WindowsDX.CSharp/.config/dotnet-tools.json b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.WindowsDX.CSharp/.config/dotnet-tools.json index a966e81a617..099bf0db52e 100644 --- a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.WindowsDX.CSharp/.config/dotnet-tools.json +++ b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.WindowsDX.CSharp/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-mgcb": { - "version": "3.8.2.1-develop", - "commands": [ - "mgcb" - ] - }, "dotnet-mgcb-editor": { "version": "3.8.2.1-develop", "commands": [ diff --git a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.iOS.CSharp/.config/dotnet-tools.json b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.iOS.CSharp/.config/dotnet-tools.json index a966e81a617..099bf0db52e 100644 --- a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.iOS.CSharp/.config/dotnet-tools.json +++ b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Application.iOS.CSharp/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-mgcb": { - "version": "3.8.2.1-develop", - "commands": [ - "mgcb" - ] - }, "dotnet-mgcb-editor": { "version": "3.8.2.1-develop", "commands": [ diff --git a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Library.CSharp/.config/dotnet-tools.json b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Library.CSharp/.config/dotnet-tools.json index a966e81a617..099bf0db52e 100644 --- a/Templates/MonoGame.Templates.CSharp/content/MonoGame.Library.CSharp/.config/dotnet-tools.json +++ b/Templates/MonoGame.Templates.CSharp/content/MonoGame.Library.CSharp/.config/dotnet-tools.json @@ -2,12 +2,6 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-mgcb": { - "version": "3.8.2.1-develop", - "commands": [ - "mgcb" - ] - }, "dotnet-mgcb-editor": { "version": "3.8.2.1-develop", "commands": [ diff --git a/Tools/MonoGame.Content.Builder.Editor/Common/PipelineController.cs b/Tools/MonoGame.Content.Builder.Editor/Common/PipelineController.cs index 8f5dbb2764e..1308e0052de 100644 --- a/Tools/MonoGame.Content.Builder.Editor/Common/PipelineController.cs +++ b/Tools/MonoGame.Content.Builder.Editor/Common/PipelineController.cs @@ -35,12 +35,17 @@ public partial class PipelineController : IController private static readonly string [] _mgcbSearchPaths = new [] { #if DEBUG +#if MAC + Path.Combine(Path.GetDirectoryName(System.AppContext.BaseDirectory) ?? "", "../../../MonoGame.Content.Builder/Debug/mgcb.dll"), + Path.Combine(Path.GetDirectoryName(System.AppContext.BaseDirectory) ?? "", "../../../../../MonoGame.Content.Builder/Debug/mgcb.dll"), +#else Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "../../../MonoGame.Content.Builder/Debug/mgcb.dll"), Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "../../../../../../MonoGame.Content.Builder/Debug/mgcb.dll"), +#endif #else #if MAC Path.Combine(Path.GetDirectoryName(System.AppContext.BaseDirectory) ?? "", "../../../MonoGame.Content.Builder/Release/mgcb.dll"), - Path.Combine(Path.GetDirectoryName(System.AppContext.BaseDirectory) ?? "", "../../../../../../MonoGame.Content.Builder/Release/mgcb.dll"), + Path.Combine(Path.GetDirectoryName(System.AppContext.BaseDirectory) ?? "", "../../../../../MonoGame.Content.Builder/Release/mgcb.dll"), #else Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "../../../MonoGame.Content.Builder/Release/mgcb.dll"), Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "", "../../../../../../MonoGame.Content.Builder/Release/mgcb.dll"), @@ -127,6 +132,7 @@ private PipelineController(IView view) LoadTemplates(templatesPath); #endif + RestoreMGCB(); UpdateMenu(); view.UpdateRecentList(PipelineSettings.Default.ProjectHistory); @@ -574,6 +580,20 @@ public void Clean() UpdateMenu(); } + private void RestoreMGCB() + { + var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + var workingDirectory = Path.Combine(appDataPath, "mgcb-dotnet-tool", version); + Directory.CreateDirectory(workingDirectory); + var dotnet = Global.Unix ? "dotnet" : "dotnet.exe"; + if (Util.Run(dotnet, $"tool install dotnet-mgcb --version {version} --tool-path .", workingDirectory) != 0) + { + // install the latest + Util.Run(dotnet, $"tool install dotnet-mgcb --tool-path .", workingDirectory); + } + } + private void DoBuild(string commands) { Encoding encoding; @@ -585,9 +605,11 @@ private void DoBuild(string commands) encoding = Encoding.UTF8; } - var mgcbCommand = "mgcb"; + + var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var version = Assembly.GetExecutingAssembly().GetName().Version.ToString (); + var mgcbCommand = Path.Combine(appDataPath, "mgcb-dotnet-tool", version, "mgcb"); var currentDir = Environment.CurrentDirectory; - foreach (var path in _mgcbSearchPaths) { var fullPath = Path.Combine(currentDir, path); @@ -598,26 +620,17 @@ private void DoBuild(string commands) break; } } + // allow the users to override the path with an environment variable + // the same as the MSBuild property in the .targets + var mgcbUserPath = Environment.GetEnvironmentVariable("MGCBCommand"); + if (!string.IsNullOrEmpty(mgcbUserPath) && File.Exists(mgcbUserPath)) + { + mgcbCommand = mgcbUserPath; + } try { - // Prepare the process. - _buildProcess = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = Global.Unix ? "dotnet" : "dotnet.exe", - Arguments = $"{mgcbCommand} {commands}", - WorkingDirectory = Path.GetDirectoryName(_project.OriginalPath), - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = false, - RedirectStandardOutput = true, - StandardOutputEncoding = encoding - } - }; - _buildProcess.OutputDataReceived += (sender, args) => View.OutputAppend(args.Data); - + _buildProcess = Util.CreateProcess(mgcbCommand, commands, Path.GetDirectoryName (_project.OriginalPath), encoding, (s) => View.OutputAppend (s)); // Fire off the process. Console.WriteLine(_buildProcess.StartInfo.FileName + " " + _buildProcess.StartInfo.Arguments); Environment.CurrentDirectory = _buildProcess.StartInfo.WorkingDirectory; diff --git a/Tools/MonoGame.Content.Builder.Editor/Common/Util.cs b/Tools/MonoGame.Content.Builder.Editor/Common/Util.cs index 8684e46ad08..3053572ed3f 100644 --- a/Tools/MonoGame.Content.Builder.Editor/Common/Util.cs +++ b/Tools/MonoGame.Content.Builder.Editor/Common/Util.cs @@ -1,6 +1,8 @@ using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Text; namespace MonoGame.Tools.Pipeline { @@ -46,5 +48,41 @@ public static string GetRelativePath(string filespec, string folder) return result; } + + public static int Run(string command, string arguments, string workingDirectory) + { + var process = CreateProcess(command, arguments, workingDirectory, Encoding.UTF8, (s) => Console.WriteLine(s)); + process.Start(); + process.BeginOutputReadLine(); + process.WaitForExit(); + return process.ExitCode; + } + + public static Process CreateProcess(string command, string arguments, string workingDirectory, Encoding encoding, Action output) + { + var exe = command; + var args = arguments; + if (command.EndsWith (".dll")) { + // we are referencing the dll directly. We need to call dotnet to host. + exe = Global.Unix ? "dotnet" : "dotnet.exe"; + args = $"{command} {arguments}"; + } + var _buildProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = exe, + Arguments = args, + WorkingDirectory = workingDirectory, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = false, + RedirectStandardOutput = true, + StandardOutputEncoding = encoding + } + }; + _buildProcess.OutputDataReceived += (sender, args) => output(args.Data); + return _buildProcess; + } } } diff --git a/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.props b/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.props index d3e96526443..daa0884a020 100644 --- a/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.props +++ b/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.props @@ -3,6 +3,7 @@ dotnet true + $(MSBuildThisFileDirectory)dotnet-tools/ mgcb diff --git a/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.targets b/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.targets index 4c2ab79c492..52f05b26a27 100644 --- a/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.targets +++ b/Tools/MonoGame.Content.Builder.Task/MonoGame.Content.Builder.Task.targets @@ -84,6 +84,12 @@ + + + + + + - + - - - + + <_Command Condition="Exists ('$(MGCBToolDirectory)\$(MGCBCommand)')">"$(MGCBToolDirectory)\$(MGCBCommand)" + + <_Command Condition=" '$(_Command)' == '' ">"$(DotnetCommand)" "$(MGCBCommand)" + diff --git a/build/BuildToolsTasks/BuildContentPipelineTask.cs b/build/BuildToolsTasks/BuildContentPipelineTask.cs index fed3f828928..2f6018bd76e 100644 --- a/build/BuildToolsTasks/BuildContentPipelineTask.cs +++ b/build/BuildToolsTasks/BuildContentPipelineTask.cs @@ -9,11 +9,5 @@ public override void Run(BuildContext context) { var builderPath = context.GetProjectPath(ProjectType.ContentPipeline); context.DotNetPack(builderPath, context.DotNetPackSettings); - - // ensure that the local development has the required dotnet tools. - // this won't actually include the tool manifest in a final build, - // but it will setup a local developer's project - context.DotNetTool(builderPath, "tool install --create-manifest-if-needed mgcb-basisu"); - context.DotNetTool(builderPath, "tool install --create-manifest-if-needed mgcb-crunch"); } } diff --git a/build/DeployTasks/UploadArtifactsTask.cs b/build/DeployTasks/UploadArtifactsTask.cs index 61abf7eceef..e738b20ffce 100644 --- a/build/DeployTasks/UploadArtifactsTask.cs +++ b/build/DeployTasks/UploadArtifactsTask.cs @@ -15,10 +15,52 @@ public override async Task RunAsync(BuildContext context) _ => "linux" }; + // Clean up build tools if installed + // otherwise we get permission issues after extraction + // because the zip removes all the permissions. + // Plus in windows hidden files (like the .store directory) + // are ignored. This causes `dotnet tool` to error. + var path = System.IO.Path.Combine(context.BuildOutput, "Tests", "Tools", "Release", "dotnet-tools"); + if (System.IO.Directory.Exists(path)) { + context.Log.Information ($"Deleting: {path}"); + System.IO.Directory.Delete (path, recursive: true); + } + if (context.IsRunningOnMacOs()) + { + path = System.IO.Path.Combine(context.BuildOutput, "Tests", "Tools", "Release", "osx"); + DeleteToolStore(context, path); + } + if (context.IsRunningOnLinux()) + { + path = System.IO.Path.Combine(context.BuildOutput, "Tests", "Tools", "Release", "linux"); + DeleteToolStore(context, path); + } + if (context.IsRunningOnWindows()) + { + path = System.IO.Path.Combine(context.BuildOutput, "Tests", "Tools", "Release"); + DeleteToolStore(context, path); + } + + await context.GitHubActions().Commands.UploadArtifact(new DirectoryPath(context.NuGetsDirectory.FullPath), $"nuget-{os}"); await context.GitHubActions().Commands.UploadArtifact(new DirectoryPath(System.IO.Path.Combine(context.BuildOutput, "Tests", "Tools", "Release")), $"tests-tools-{os}"); await context.GitHubActions().Commands.UploadArtifact(new DirectoryPath(System.IO.Path.Combine(context.BuildOutput, "Tests", "DesktopGL", "Release")), $"tests-desktopgl-{os}"); if (context.IsRunningOnWindows()) await context.GitHubActions().Commands.UploadArtifact(new DirectoryPath(System.IO.Path.Combine(context.BuildOutput, "Tests", "WindowsDX", "Release")), $"tests-windowsdx-{os}"); } + + void DeleteToolStore(BuildContext context, string path) + { + if (System.IO.Directory.Exists(path)) { + var store = System.IO.Path.Combine (path, ".store"); + if (System.IO.Directory.Exists(store)) { + context.Log.Information($"Deleting: {store}"); + System.IO.Directory.Delete(store, recursive: true); + foreach (var file in System.IO.Directory.GetFiles(path, "mgcb-*", System.IO.SearchOption.TopDirectoryOnly)) { + context.Log.Information($"Deleting: {file}"); + System.IO.File.Delete(file); + } + } + } + } }