Files
LuaCsForBarotraumaEP/Libraries/MonoGame.Framework/Src/MonoGame.Framework.Content.Pipeline/ExternalTool.cs
2019-06-25 16:00:44 +03:00

192 lines
6.8 KiB
C#

// MonoGame - Copyright (C) The MonoGame Team
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using MonoGame.Utilities;
namespace Microsoft.Xna.Framework.Content.Pipeline
{
/// <summary>
/// Helper to run an external tool installed in the system. Useful for when
/// we don't want to package the tool ourselves (ffmpeg) or it's provided
/// by a third party (console manufacturer).
/// </summary>
internal class ExternalTool
{
public static int Run(string command, string arguments)
{
string stdout, stderr;
var result = Run(command, arguments, out stdout, out stderr);
if (result < 0)
throw new Exception(string.Format("{0} returned exit code {1}", command, result));
return result;
}
public static int Run(string command, string arguments, out string stdout, out string stderr, string stdin = null)
{
// 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(command);
if (string.IsNullOrEmpty(fullPath))
throw new Exception(string.Format("Couldn't locate external tool '{0}'.", command));
// 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
{
Arguments = arguments,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
FileName = fullPath,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
};
EnsureExecutable(fullPath);
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;
}
}
/// <summary>
/// Returns the fully-qualified path for a command, searching the system path if necessary.
/// </summary>
/// <remarks>
/// It's apparently necessary to use the full path when running on some systems.
/// </remarks>
private static string FindCommand(string command)
{
// Expand any environment variables.
command = Environment.ExpandEnvironmentVariables(command);
// If we have a full path just pass it through.
if (File.Exists(command))
return command;
// We don't have a full path, so try running through the system path to find it.
var paths = AppDomain.CurrentDomain.BaseDirectory +
Path.PathSeparator +
Environment.GetEnvironmentVariable("PATH");
var justTheName = Path.GetFileName(command);
foreach (var path in paths.Split(Path.PathSeparator))
{
var fullName = Path.Combine(path, justTheName);
if (File.Exists(fullName))
return fullName;
if (CurrentPlatform.OS == OS.Windows)
{
var fullExeName = string.Concat(fullName, ".exe");
if (File.Exists(fullExeName))
return fullExeName;
}
}
return null;
}
/// <summary>
/// Ensures the specified executable has the executable bit set. If the
/// executable doesn't have the executable bit set on Linux or Mac OS, then
/// Mono will refuse to execute it.
/// </summary>
/// <param name="path">The full path to the executable.</param>
private static void EnsureExecutable(string path)
{
#if LINUX || MACOS
if (path == "/bin/bash")
return;
try
{
var p = Process.Start("chmod", "u+x '" + path + "'");
p.WaitForExit();
}
catch
{
// This platform may not have chmod in the path, in which case we can't
// do anything reasonable here.
}
#endif
}
/// <summary>
/// Safely deletes the file if it exists.
/// </summary>
/// <param name="filePath">The path to the file to delete.</param>
public static void DeleteFile(string filePath)
{
try
{
File.Delete(filePath);
}
catch (Exception)
{
}
}
}
}