Skip to content

Commit d17a7a3

Browse files
rmarinhoCopilot
andcommitted
Add shared helpers: ThrowIfFailed, ValidateNotNullOrEmpty, FindCmdlineTool
- ProcessUtils.ThrowIfFailed: consistent exit code checking with stderr/stdout context - ProcessUtils.ValidateNotNullOrEmpty: reusable null/empty argument validation - ProcessUtils.FindCmdlineTool: version-aware cmdline-tools directory search with legacy fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3a788bb commit d17a7a3

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,73 @@ static string JoinArguments (string[] args)
203203
}
204204
#endif
205205

206+
/// <summary>
207+
/// Throws <see cref="InvalidOperationException"/> when <paramref name="exitCode"/> is non-zero.
208+
/// Includes stderr/stdout context in the message when available.
209+
/// </summary>
210+
public static void ThrowIfFailed (int exitCode, string command, string? stderr = null, string? stdout = null)
211+
{
212+
if (exitCode == 0)
213+
return;
214+
215+
var message = $"'{command}' failed with exit code {exitCode}.";
216+
217+
if (!string.IsNullOrEmpty (stderr))
218+
message += $" stderr:{Environment.NewLine}{stderr!.Trim ()}";
219+
if (!string.IsNullOrEmpty (stdout))
220+
message += $" stdout:{Environment.NewLine}{stdout!.Trim ()}";
221+
222+
throw new InvalidOperationException (message);
223+
}
224+
225+
/// <summary>
226+
/// Validates that <paramref name="value"/> is not null or empty.
227+
/// Throws <see cref="ArgumentNullException"/> for null values and
228+
/// <see cref="ArgumentException"/> for empty strings.
229+
/// </summary>
230+
public static void ValidateNotNullOrEmpty (string? value, string paramName)
231+
{
232+
if (value is null)
233+
throw new ArgumentNullException (paramName);
234+
if (value.Length == 0)
235+
throw new ArgumentException ("Value cannot be an empty string.", paramName);
236+
}
237+
238+
/// <summary>
239+
/// Searches versioned cmdline-tools directories (descending) and "latest" for a specific tool binary.
240+
/// Falls back to the legacy tools/bin path. Returns null if not found.
241+
/// </summary>
242+
public static string? FindCmdlineTool (string sdkPath, string toolName, string extension)
243+
{
244+
var cmdlineToolsDir = Path.Combine (sdkPath, "cmdline-tools");
245+
246+
if (Directory.Exists (cmdlineToolsDir)) {
247+
var subdirs = new List<(string name, Version? version)> ();
248+
foreach (var dir in Directory.GetDirectories (cmdlineToolsDir)) {
249+
var name = Path.GetFileName (dir);
250+
if (string.IsNullOrEmpty (name) || name == "latest")
251+
continue;
252+
Version.TryParse (name, out var v);
253+
subdirs.Add ((name, v ?? new Version (0, 0)));
254+
}
255+
subdirs.Sort ((a, b) => b.version!.CompareTo (a.version));
256+
257+
// Check versioned directories first (highest version first), then "latest"
258+
foreach (var (name, _) in subdirs) {
259+
var toolPath = Path.Combine (cmdlineToolsDir, name, "bin", toolName + extension);
260+
if (File.Exists (toolPath))
261+
return toolPath;
262+
}
263+
var latestPath = Path.Combine (cmdlineToolsDir, "latest", "bin", toolName + extension);
264+
if (File.Exists (latestPath))
265+
return latestPath;
266+
}
267+
268+
// Legacy fallback: tools/bin/<tool>
269+
var legacyPath = Path.Combine (sdkPath, "tools", "bin", toolName + extension);
270+
return File.Exists (legacyPath) ? legacyPath : null;
271+
}
272+
206273
internal static IEnumerable<string> FindExecutablesInPath (string executable)
207274
{
208275
var path = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? "";

0 commit comments

Comments
 (0)