diff --git a/MonoGame.Tool.BuildScripts.csproj b/MonoGame.Tool.BuildScripts.csproj
index 4c9ff77..8be078f 100644
--- a/MonoGame.Tool.BuildScripts.csproj
+++ b/MonoGame.Tool.BuildScripts.csproj
@@ -9,19 +9,9 @@
-
+
PreserveNewest
- Icon.png
-
-
-
- PreserveNewest
- MonoGame.Tool.X.txt
-
-
-
- PreserveNewest
- Program.txt
+ %(Filename)%(Extension)
diff --git a/PackContext.cs b/PackContext.cs
index 0eb5383..633ba82 100644
--- a/PackContext.cs
+++ b/PackContext.cs
@@ -7,7 +7,7 @@ public class PackContext
{
public string ToolName { get; }
- public string CommandName { get; }
+ public string Description { get; }
public string ExecutableName { get; }
@@ -24,10 +24,11 @@ public class PackContext
public PackContext(ICakeContext context)
{
ToolName = context.Argument("toolname", "X");
- CommandName = context.Argument("commandname", "X");
+ ToolName = char.ToUpper(ToolName[0]) + ToolName[1..];
ExecutableName = context.Argument("executablename", "X");
LicensePath = context.Argument("licensepath", "");
Version = context.Argument("version", "1.0.0");
+ Description = $"This package contains executables for {ToolName} built for usage with MonoGame.";
RepositoryUrl = "X";
IsTag = false;
diff --git a/Resources/MonoGame.Tool.X.targets b/Resources/MonoGame.Tool.X.targets
new file mode 100644
index 0000000..53baa61
--- /dev/null
+++ b/Resources/MonoGame.Tool.X.targets
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/Resources/MonoGame.Tool.X.txt b/Resources/MonoGame.Tool.X.txt
index 5c2c9cd..ac15a39 100644
--- a/Resources/MonoGame.Tool.X.txt
+++ b/Resources/MonoGame.Tool.X.txt
@@ -1,35 +1,27 @@
- Exe
net8.0
true
enable
- Major
-
-
-
MonoGame build of {X} Tool
{Description}
-
-
-
- true
Icon.png
- {CommandName}
{LicenseName}
- {ReadMeName}
- {CommandName}
-
+
- {ContentInclude}
+
+
+
+
+
diff --git a/Resources/Program.txt b/Resources/Program.txt
index b1c0413..1954215 100644
--- a/Resources/Program.txt
+++ b/Resources/Program.txt
@@ -1,55 +1,116 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
-for(int i = 0; i < args.Length; i++)
+namespace MonoGame.Tool;
+
+public class {X}
{
- if(args[i].Contains(" "))
+ static string FindCommand(string commandid)
{
- args[i] = $"\"{args[i]}\"";
- }
-}
+ var baseDir = Path.GetDirectoryName(typeof({X}).Assembly.Location) ?? "";
-string arguments = string.Join(" ", args);
-string baseDirectory = AppContext.BaseDirectory;
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return Path.Combine(baseDir, "windows-x64", commandid);
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ var osxPath = Path.Combine(baseDir, "osx", commandid);
+ if (!File.Exists(osxPath))
+ {
+ osxPath = RuntimeInformation.ProcessArchitecture switch
+ {
+ Architecture.Arm or Architecture.Arm64 => Path.Combine(baseDir, "osx-arm64", commandid),
+ _ => Path.Combine(baseDir, "osx-x64", commandid)
+ };
+ }
+ return osxPath;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return Path.Combine(baseDir, "linux-x64", commandid);
+ }
-ProcessStartInfo startInfo = new ProcessStartInfo()
-{
- Arguments = arguments,
- UseShellExecute = false
-};
+ return commandid;
+ }
-if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-{
- startInfo.FileName = Path.Combine(baseDirectory, "binaries", "windows-x64", "{ExecutableName}");
-}
-else if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
-{
- startInfo.FileName = Path.Combine(baseDirectory, "binaries", "linux-x64", "{ExecutableName}");
-}
-else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-{
- var osxPath = Path.Combine(baseDirectory, "binaries", "osx", "{ExecutableName}");
- if(!File.Exists(osxPath))
+ public static int Run(string arguments, out string stdout, out string stderr, string? stdin = null, string? workingDirectory = null)
{
- osxPath = RuntimeInformation.ProcessArchitecture switch
+ // This particular case is likely to be the most common and thus
+ // warrants its own specific error message rather than falling
+ // back to a general exception from Process.Start()
+ var fullPath = FindCommand("{ExecutableName}");
+
+ // We can't reference ref or out parameters from within
+ // lambdas (for the thread functions), so we have to store
+ // the data in a temporary variable and then assign these
+ // variables to the out parameters.
+ var stdoutTemp = string.Empty;
+ var stderrTemp = string.Empty;
+
+ var processInfo = new ProcessStartInfo
{
- Architecture.Arm or Architecture.Arm64 => Path.Combine(baseDirectory, "binaries", "osx-arm64", "{ExecutableName}"),
- _ => Path.Combine(baseDirectory, "binaries", "osx-x64", "{ExecutableName}")
+ Arguments = arguments,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false,
+ FileName = fullPath,
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = true,
};
- }
- startInfo.FileName = osxPath;
-}
-using (Process? process = Process.Start(startInfo))
-{
- if (process is not null)
- {
- await process.WaitForExitAsync();
+ if (!string.IsNullOrWhiteSpace(workingDirectory))
+ {
+ processInfo.WorkingDirectory = workingDirectory;
+ }
+
+ using var process = new Process();
+ process.StartInfo = processInfo;
+ process.Start();
+
+ // We have to run these in threads, because using ReadToEnd
+ // on one stream can deadlock if the other stream's buffer is
+ // full.
+ var stdoutThread = new Thread(new ThreadStart(() =>
+ {
+ var memory = new MemoryStream();
+ process.StandardOutput.BaseStream.CopyTo(memory);
+ var bytes = new byte[memory.Position];
+ memory.Seek(0, SeekOrigin.Begin);
+ memory.Read(bytes, 0, bytes.Length);
+ stdoutTemp = System.Text.Encoding.ASCII.GetString(bytes);
+ }));
+ var stderrThread = new Thread(new ThreadStart(() =>
+ {
+ var memory = new MemoryStream();
+ process.StandardError.BaseStream.CopyTo(memory);
+ var bytes = new byte[memory.Position];
+ memory.Seek(0, SeekOrigin.Begin);
+ memory.Read(bytes, 0, bytes.Length);
+ stderrTemp = System.Text.Encoding.ASCII.GetString(bytes);
+ }));
+
+ stdoutThread.Start();
+ stderrThread.Start();
+
+ if (stdin != null)
+ {
+ process.StandardInput.Write(System.Text.Encoding.ASCII.GetBytes(stdin));
+ }
+
+ // Make sure interactive prompts don't block.
+ process.StandardInput.Close();
+
+ process.WaitForExit();
+
+ stdoutThread.Join();
+ stderrThread.Join();
+
+ stdout = stdoutTemp;
+ stderr = stderrTemp;
+
return process.ExitCode;
}
- else
- {
- // unable to start process
- return 1;
- }
}
diff --git a/Tasks/PublishPackageTask.cs b/Tasks/PackageTask.cs
similarity index 66%
rename from Tasks/PublishPackageTask.cs
rename to Tasks/PackageTask.cs
index 4b4f0b2..b7cebb8 100644
--- a/Tasks/PublishPackageTask.cs
+++ b/Tasks/PackageTask.cs
@@ -1,12 +1,12 @@
-
using System.Runtime.InteropServices;
using Cake.Common.Tools.DotNet.NuGet.Push;
namespace BuildScripts;
[TaskName("Package")]
-public sealed class PublishPackageTask : AsyncFrostingTask
+public sealed class PackageTask : AsyncFrostingTask
{
+
public override async Task RunAsync(BuildContext context)
{
// Create a temporary directory tha we can use to build the "project" in that we'll pack into a dotnet tool
@@ -17,9 +17,9 @@ public override async Task RunAsync(BuildContext context)
// local artifacts so we can test/run this locally as well
if (context.BuildSystem().IsRunningOnGitHubActions)
{
- var requiredRids = context.IsUniversalBinary ?
- new string[] { "windows-x64", "linux-x64", "osx" } :
- new string[] { "windows-x64", "linux-x64", "osx-x64", "osx-arm64" };
+ string[] requiredRids = context.IsUniversalBinary ?
+ ["windows-x64", "linux-x64", "osx"] :
+ ["windows-x64", "linux-x64", "osx-x64", "osx-arm64"];
foreach (var rid in requiredRids)
{
@@ -57,38 +57,14 @@ public override async Task RunAsync(BuildContext context)
}
// Create the temporary project that we'll use to pack into the dotnet tool
- var licensePath = context.PackContext.LicensePath;
- var licenseName = "LICENSE";
-
- if (licensePath.EndsWith(".txt")) licenseName += ".txt";
- else if (licensePath.EndsWith(".md")) licenseName += ".md";
+ var projectPath = $"{projectDir}/MonoGame.Tool.{context.PackContext.ToolName}.csproj";
+ await WriteEmbeddedResource(context, "MonoGame.Tool.X.txt", projectPath);
+ await WriteEmbeddedResource(context, "MonoGame.Tool.X.targets", $"{projectDir}/MonoGame.Tool.{context.PackContext.ToolName}.targets");
+ await WriteEmbeddedResource(context, "Program.txt", $"{projectDir}/Program.cs");
var readMeName = "README.md";
var readMePath = $"{projectDir}/{readMeName}";
-
- var description = $"This package contains executables for {context.PackContext.ToolName} built for usage with MonoGame.";
-
- var contentInclude = $"";
-
- var projectData = await ReadEmbeddedResourceAsync("MonoGame.Tool.X.txt");
- projectData = projectData.Replace("{X}", context.PackContext.ToolName)
- .Replace("{Description}", description)
- .Replace("{CommandName}", context.PackContext.CommandName)
- .Replace("{LicensePath}", context.PackContext.LicensePath)
- .Replace("{ReadMePath}", readMeName)
- .Replace("{LicenseName}", licenseName)
- .Replace("{ReadMeName}", readMeName)
- .Replace("{ContentInclude}", contentInclude);
-
- string projectPath = $"{projectDir}/MonoGame.Tool.{context.PackContext.ToolName}.csproj";
- await File.WriteAllTextAsync(projectPath, projectData);
-
- var programData = await ReadEmbeddedResourceAsync("Program.txt");
- programData = programData.Replace("{ExecutableName}", context.PackContext.ExecutableName);
- var programPath = $"{projectDir}/Program.cs";
- await File.WriteAllTextAsync(programPath, programData);
-
- await File.WriteAllTextAsync(readMePath, description);
+ await File.WriteAllTextAsync(readMePath, context.PackContext.Description);
await SaveEmbeddedResourceAsync("Icon.png", $"{projectDir}/Icon.png");
@@ -131,9 +107,9 @@ public override async Task RunAsync(BuildContext context)
private static async Task RunOnGithubAsync(BuildContext context, string projectDir)
{
// Download remote artifacts from github
- var requiredRids = context.IsUniversalBinary ?
- new string[] { "windows-x64", "linux-x64", "osx" } :
- new string[] { "windows-x64", "linux-x64", "osx-x64", "osx-arm64" };
+ string[] requiredRids = context.IsUniversalBinary ?
+ ["windows-x64", "linux-x64", "osx" ]:
+ ["windows-x64", "linux-x64", "osx-x64", "osx-arm64"];
foreach (var rid in requiredRids)
{
@@ -146,11 +122,26 @@ private static async Task RunOnGithubAsync(BuildContext context, string projectD
}
}
- private static async Task ReadEmbeddedResourceAsync(string resourceName)
+ private static async Task WriteEmbeddedResource(BuildContext context, string resource, string outputPath)
{
- await using var stream = typeof(PublishPackageTask).Assembly.GetManifestResourceStream(resourceName)!;
+ var licenseName = System.IO.Path.GetExtension(context.PackContext.LicensePath) switch
+ {
+ ".txt" => "LICENSE.txt",
+ ".md" => "LICENSE.md",
+ _ => "LICENSE"
+ };
+ var contentInclude = $"";
+ await using var stream = typeof(PackageTask).Assembly.GetManifestResourceStream(resource)!;
using var reader = new StreamReader(stream);
- return await reader.ReadToEndAsync();
+ var outputData = (await reader.ReadToEndAsync())
+ .Replace("{X}", context.PackContext.ToolName)
+ .Replace("{x}", context.PackContext.ToolName.ToLower())
+ .Replace("{ExecutableName}", context.PackContext.ExecutableName)
+ .Replace("{Description}", context.PackContext.Description)
+ .Replace("{LicensePath}", context.PackContext.LicensePath)
+ .Replace("{LicenseName}", licenseName)
+ .Replace("{ContentInclude}", contentInclude);
+ await File.WriteAllTextAsync(outputPath, outputData);
}
private static async Task SaveEmbeddedResourceAsync(string resourceName, string outPath)
@@ -158,7 +149,7 @@ private static async Task SaveEmbeddedResourceAsync(string resourceName, string
if (File.Exists(outPath))
File.Delete(outPath);
- await using var stream = typeof(PublishPackageTask).Assembly.GetManifestResourceStream(resourceName)!;
+ await using var stream = typeof(PackageTask).Assembly.GetManifestResourceStream(resourceName)!;
await using var writer = File.Create(outPath);
await stream.CopyToAsync(writer);
writer.Close();