Skip to content

Comments

Add AvdManagerRunner for avdmanager CLI operations#282

Open
rmarinho wants to merge 4 commits intomainfrom
feature/avdmanager-runner
Open

Add AvdManagerRunner for avdmanager CLI operations#282
rmarinho wants to merge 4 commits intomainfrom
feature/avdmanager-runner

Conversation

@rmarinho
Copy link
Member

Summary

Adds AvdManagerRunner class to wrap avdmanager CLI operations for programmatic AVD management.

Addresses #277

New API

public class AvdManagerRunner
{
    // List installed AVDs
    Task<IReadOnlyList<AvdInfo>> ListAvdsAsync(CancellationToken ct = default);
    
    // Create a new AVD
    Task<ToolRunnerResult> CreateAvdAsync(string name, string device, string systemImage, CancellationToken ct = default);
    
    // Delete an AVD
    Task<ToolRunnerResult> DeleteAvdAsync(string name, CancellationToken ct = default);
    
    // List available device definitions
    Task<IReadOnlyList<string>> ListDevicesAsync(CancellationToken ct = default);
    
    // List available system image targets
    Task<IReadOnlyList<string>> ListTargetsAsync(CancellationToken ct = default);
}

public record AvdInfo(string Name, string Device, string Path, string Target, string Abi);

Usage

var runner = new AvdManagerRunner(androidSdkPath, javaHomePath);

// List AVDs
var avds = await runner.ListAvdsAsync();

// Create AVD
await runner.CreateAvdAsync(""Pixel_6_API_33"", ""pixel_6"", ""system-images;android-33;google_apis;x86_64"");

// Delete AVD
await runner.DeleteAvdAsync(""Pixel_6_API_33"");

Dependencies

Requires #281 (base tool runner infrastructure) to be merged first.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

- AndroidToolRunner: Base class for running Android SDK CLI tools
- AndroidEnvironmentHelper: Environment variable setup, ABI mapping
- ToolRunnerResult: Generic result model for tool execution

This provides the foundation for specific tool runners.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 23, 2026 17:39
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “runner” layer to Xamarin.Android.Tools.AndroidSdk to execute Android SDK command-line tools programmatically, with an initial AvdManagerRunner implementation for creating/listing/deleting AVDs.

Changes:

  • Introduces AndroidToolRunner for running SDK tools with timeout/stdout/stderr capture (sync + async) and a background-start helper.
  • Adds AvdManagerRunner to locate and invoke avdmanager, parse list avd output, and enrich results from config.ini.
  • Adds supporting models (ToolRunnerResult, AvdInfo) and environment setup utilities (AndroidEnvironmentHelper).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs Adds an avdmanager wrapper with list/create/delete and output parsing/enrichment.
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs Adds process execution utilities (sync/async + background start).
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs Provides JAVA_HOME / ANDROID_HOME setup plus ABI/API/tag mapping helpers.
src/Xamarin.Android.Tools.AndroidSdk/Models/ToolRunnerResult.cs Introduces a result type for tool execution (typed + untyped).
src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs Adds an AVD info model used by AvdManagerRunner.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 265 to 271
return new AvdInfo {
Name = name,
DeviceProfile = device,
Path = path,
SystemImage = target,
Target = target
};
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateAvdFromParsedData assigns SystemImage = target and Target = target. The parsed value here is the human-readable “Target:”/“Based on:” text, not the system image package path (e.g., system-images;android-33;...). This makes AvdInfo.SystemImage misleading for listed AVDs; consider leaving SystemImage null here or parsing the actual system image from config.ini (image.sysdir.1) and keeping Target as the display string.

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +114
"google_apis_playstore" => "Google API's, Play Store",
"google_apis" => playStoreEnabled ? "Google API's, Play Store" : "Google API's",
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MapTagIdToDisplayName uses "Google API's" (apostrophe) in user-facing strings; this is grammatically incorrect and inconsistent with common naming (“Google APIs”). Update the strings to “Google APIs” / “Google APIs, Play Store”.

Suggested change
"google_apis_playstore" => "Google API's, Play Store",
"google_apis" => playStoreEnabled ? "Google API's, Play Store" : "Google API's",
"google_apis_playstore" => "Google APIs, Play Store",
"google_apis" => playStoreEnabled ? "Google APIs, Play Store" : "Google APIs",

Copilot uses AI. Check for mistakes.
Comment on lines 9 to 20
public class AvdInfo
{
/// <summary>
/// AVD name (unique identifier).
/// </summary>
public string Name { get; set; } = string.Empty;

/// <summary>
/// Device profile (e.g., "pixel_6", "Nexus 5X").
/// </summary>
public string? DeviceProfile { get; set; }

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AvdInfo is described in the PR as a record with a small fixed shape, but the implementation is a mutable class with different property names (DeviceProfile/SystemImage/etc.) and additional fields. This is a public API surface difference; either align the type/signature with the PR description or update the PR description so downstream consumers (and source compatibility expectations) match reality.

Copilot uses AI. Check for mistakes.
Comment on lines 208 to 261
List<AvdInfo> ParseAvdList (string output)
{
var avds = new List<AvdInfo> ();
var lines = output.Split ('\n');

string? currentName = null;
string? currentDevice = null;
string? currentPath = null;
string? currentTarget = null;

foreach (var line in lines) {
var trimmed = line.Trim ();

if (trimmed.StartsWith ("Name:")) {
// Save previous AVD if exists
if (currentName != null) {
var avd = CreateAvdFromParsedData (currentName, currentDevice, currentPath, currentTarget);
avds.Add (avd);
}
currentName = trimmed.Substring (5).Trim ();
currentDevice = null;
currentPath = null;
currentTarget = null;
} else if (trimmed.StartsWith ("Device:")) {
currentDevice = trimmed.Substring (7).Trim ();
} else if (trimmed.StartsWith ("Path:")) {
currentPath = trimmed.Substring (5).Trim ();
} else if (trimmed.StartsWith ("Target:")) {
currentTarget = trimmed.Substring (7).Trim ();
} else if (trimmed.StartsWith ("Based on:")) {
if (currentTarget == null)
currentTarget = trimmed.Substring (9).Trim ();
}
}

// Don't forget the last one
if (currentName != null) {
var avd = CreateAvdFromParsedData (currentName, currentDevice, currentPath, currentTarget);
avds.Add (avd);
}

// Enrich AVDs with config.ini data
for (int i = 0; i < avds.Count; i++) {
var avd = avds [i];
if (!string.IsNullOrEmpty (avd.Path)) {
var configPath = Path.Combine (avd.Path, "config.ini");
if (File.Exists (configPath)) {
avds [i] = EnrichAvdFromConfig (avd, configPath);
}
}
}

return avds;
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParseAvdList and EnrichAvdFromConfig implement non-trivial parsing logic of avdmanager output and config.ini. There are existing unit tests in this repo; adding focused tests for representative avdmanager outputs (including Windows/Unix newlines and multiple AVD entries) would help prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines 48 to 56
process.StartInfo = new ProcessStartInfo {
FileName = fileName,
Arguments = arguments,
WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AndroidToolRunner sets UseShellExecute=false and executes the provided fileName directly. On Windows, avdmanager/sdkmanager are typically .bat scripts, which cannot be launched this way (CreateProcess will fail). Consider detecting .bat/.cmd and invoking via cmd.exe /c (while still capturing output), or otherwise adjusting execution so cmdline-tools work on Windows.

Copilot uses AI. Check for mistakes.
Comment on lines 34 to 41
public static ToolRunnerResult Run (
string fileName,
string arguments = "",
string? workingDirectory = null,
Dictionary<string, string>? environmentVariables = null,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default)
{
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run(...) accepts a CancellationToken but never observes it (no registration / no early exit). This makes the API misleading and can leave callers unable to cancel long-running synchronous operations. Either honor cancellation (kill process on cancellation) or remove the parameter.

Copilot uses AI. Check for mistakes.
Comment on lines 287 to 288
RedirectStandardOutput = true,
RedirectStandardError = true,
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StartBackground redirects stdout/stderr but never reads from them. This can cause the child process to block once OS pipe buffers fill (common for emulator output), effectively hanging the background process. For true background start, avoid redirection or actively drain the streams.

Suggested change
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardOutput = false,
RedirectStandardError = false,

Copilot uses AI. Check for mistakes.
Comment on lines 16 to 20
public class AvdManagerRunner
{
readonly Func<string?> getSdkPath;
readonly Func<string?> getJdkPath;

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description advertises additional APIs (e.g., ListDevicesAsync/ListTargetsAsync) and different return types, but AvdManagerRunner currently only implements list/create/delete and returns ToolRunnerResult-wrapped results. Either implement the missing APIs or update the PR description/new API section so consumers don’t code against an API that isn’t present.

Copilot uses AI. Check for mistakes.
@rmarinho rmarinho added the copilot `copilot-cli` or other AIs were used to author this label Feb 23, 2026
@jonathanpeppers
Copy link
Member

I'd like to get the System.Diagnostics.Process code unified like mentioned here:

rmarinho and others added 3 commits February 24, 2026 14:04
Addresses PR #281 feedback from @jonathanpeppers:
> 'This is inventing lots of new code that just wraps
> System.Diagnostics.Process. Can we just use the existing code
> for this instead? ProcessUtils.cs'

Changes:
- Extended ProcessUtils with RunToolAsync() and StartToolBackground()
- Added GetExecutableExtension() and GetBatchExtension() utilities
- Removed AndroidToolRunner.cs (-347 lines)
- Removed ToolRunnerResult.cs (-74 lines)

The new methods leverage the existing StartProcess() implementation
which already handles cancellation, output redirection, and process
lifecycle correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- AvdManagerRunner: Create/delete/list Android Virtual Devices
- AvdInfo: Model for AVD metadata

API:
- ListAvdsAsync(): List installed AVDs
- CreateAvdAsync(): Create a new AVD
- DeleteAvdAsync(): Delete an AVD
- ListDevicesAsync(): List available device definitions
- ListTargetsAsync(): List available system image targets

Addresses #277

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses PR #282 feedback to use existing ProcessUtils instead of
the removed AndroidToolRunner. Simplifies API:

- Methods now throw InvalidOperationException on failure instead of
  returning result types with error states
- Uses ProcessUtils.RunToolAsync() for all tool invocations
- Simplified AvdInfo model
- Removed complex ToolRunnerResult<T> wrapper types

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/avdmanager-runner branch from b9b08f7 to d9a65ca Compare February 24, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants