forked from MonoGame/MonoGame
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ExternalTool.cs
198 lines (170 loc) · 7.3 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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 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.Framework.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;
// For Linux check specific subfolder
var lincom = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "linux", command);
if (CurrentPlatform.OS == OS.Linux && File.Exists(lincom))
return lincom;
// For Mac check specific subfolder
var maccom = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "osx", command);
if (CurrentPlatform.OS == OS.MacOSX && File.Exists(maccom))
return maccom;
// 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 (!path.StartsWith("/home") && !path.StartsWith("/Users"))
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.
}
}
/// <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)
{
}
}
}
}