forked from MonoGame/MonoGame
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ExternalTool.cs
161 lines (138 loc) · 5.83 KB
/
ExternalTool.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// 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;
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,
};
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 WINDOWS
var fullExeName = string.Concat(fullName, ".exe");
if (File.Exists(fullExeName))
return fullExeName;
#endif
}
return null;
}
/// <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)
{
}
}
}
}