Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a new EmulatorRunner to Xamarin.Android.Tools.AndroidSdk intended to wrap Android emulator CLI operations, alongside new shared infrastructure for running Android SDK command-line tools with environment setup and result modeling.
Changes:
- Added
EmulatorRunnerto start an AVD, stop an emulator, and list available AVD names. - Added
AndroidToolRunnerutility to run SDK tools sync/async (with timeouts) and to start long-running background processes. - Added
AndroidEnvironmentHelperandToolRunnerResult/ToolRunnerResult<T>to standardize tool environment and execution results.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs | Introduces emulator wrapper methods (start/stop/list AVDs) built on the tool runner infrastructure. |
| src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs | Adds process execution helpers (sync/async + background) with timeout/output capture. |
| src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs | Adds env var setup and mapping helpers (ABI/API/tag display names). |
| src/Xamarin.Android.Tools.AndroidSdk/Models/ToolRunnerResult.cs | Adds a shared result model for tool execution. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs
Outdated
Show resolved
Hide resolved
|
I'd like to get the System.Diagnostics.Process code unified like mentioned here: |
Addresses PR #284 feedback to use existing ProcessUtils instead of the removed AndroidToolRunner. Simplifies API: - Methods now throw InvalidOperationException on failure - Uses ProcessUtils.RunToolAsync() and StartToolBackground() - Removed complex ToolRunnerResult wrapper types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f1aa44f to
826d4aa
Compare
Addresses PR #283/#284 feedback to use existing ProcessUtils. Simplifies API by throwing exceptions on failure instead of returning result types with error states. Changes: - AdbRunner: Simplified using ProcessUtils.RunToolAsync() - EmulatorRunner: Uses ProcessUtils.StartToolBackground() - Removed duplicate AndroidDeviceInfo from Models directory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
39617c8 to
5268300
Compare
1b10889 to
ee31e4b
Compare
ee31e4b to
3a788bb
Compare
Review feedback addressed — commit references
New files:
Modified:
Draft dotnet/android consumer PR to follow. |
jonathanpeppers
left a comment
There was a problem hiding this comment.
🤖 AI Review Summary
Found 7 issues: 1 correctness, 2 error handling, 1 API design, 1 code duplication, 1 code organization, 1 naming.
- Correctness:
StartAvdredirects stdout/stderr but never drains the pipes — OS buffer fill will deadlock the emulator process (EmulatorRunner.cs:74) - API design:
AdditionalArgsis a singlestring— will be treated as one argument byProcessUtils.ArgumentList, breaking multi-token args like-gpu swiftshader_indirect(EmulatorBootOptions.cs:14) - Error handling:
ListDevicesAsyncignores the exit code fromProcessUtils.StartProcesswhile sibling methods inAvdManagerRunnercheck it consistently (AdbRunner.cs:72) - Code duplication:
AvdManagerRunner.AvdManagerPathreimplements the cmdline-tools version scanning thatProcessUtils.FindCmdlineTool(added in this same PR) already provides (AvdManagerRunner.cs:33) - Error handling: Bare
catch { }swallows all exceptions without capturing them (AdbRunner.cs:107)
👍 Solid three-phase boot logic ported faithfully from dotnet/android. Good use of virtual on AdbRunner methods to enable clean test mocking. Thorough test coverage with 13+ unit tests covering parsing, edge cases, and the full boot flow. Nice extraction of AndroidEnvironmentHelper for shared env var setup.
This review was generated by the android-tools-reviewer skill based on review guidelines established by @jonathanpeppers.
| // Redirect stdout/stderr so the emulator process doesn't inherit the | ||
| // caller's pipes. Without this, parent processes (e.g. VS Code spawn) | ||
| // never see the 'close' event because the emulator holds the pipes open. | ||
| psi.RedirectStandardOutput = true; |
There was a problem hiding this comment.
🤖 Pattern / Correctness — RedirectStandardOutput and RedirectStandardError are set to true but the output is never drained. When the emulator writes enough output to fill the OS pipe buffer (~4–64 KB depending on platform), the write will block and the emulator process will deadlock.
Since StartAvd is a fire-and-forget launch that returns the Process to the caller, the simplest fix is to call BeginOutputReadLine() and BeginErrorReadLine() immediately after Start() to asynchronously discard the output:
process.Start ();
process.BeginOutputReadLine ();
process.BeginErrorReadLine ();Alternatively, if you don't need to redirect at all (just prevent pipe inheritance), you could set Redirect* = false and rely on UseShellExecute = false (already set by ProcessUtils.CreateProcessStartInfo) which doesn't inherit the caller's console.
Also: using new Process { StartInfo = psi } + process.Start() directly bypasses ProcessUtils. This is an intentional pattern for long-running background processes, but worth a brief comment explaining why ProcessUtils.StartProcess isn't used here (it awaits completion).
Rule: Don't redirect stdout/stderr without draining (Postmortem #48); Use ProcessUtils (Postmortem #15)
| public class EmulatorBootOptions | ||
| { | ||
| public TimeSpan BootTimeout { get; set; } = TimeSpan.FromSeconds (300); | ||
| public string? AdditionalArgs { get; set; } |
There was a problem hiding this comment.
🤖 API design — AdditionalArgs is a single string? that gets passed directly to args.Add(additionalArgs) in StartAvd. Since ProcessUtils.CreateProcessStartInfo uses ArgumentList on net5+ (no shell parsing), a string like ""-gpu swiftshader_indirect"" becomes one argument instead of two separate ones.
Consider changing this to IEnumerable<string>? so each argument is passed separately:
public IEnumerable<string>? AdditionalArgs { get; set; }And in StartAvd:
if (options.AdditionalArgs != null)
args.AddRange (options.AdditionalArgs);This is safer and consistent with how ProcessUtils is designed to work.
Rule: Structured args, not string interpolation (Postmortem #50)
src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs
Outdated
Show resolved
Hide resolved
This skill let's you say:
review this PR: #284
Some example code reviews:
* #283 (review)
* #284 (review)
This is built off a combination of previous code reviews, saved in
`docs/CODE_REVIEW_POSTMORTEM.md`, and the review rules in
`references/review-rules.md`.
This skill lets you say:
review this PR: #284
Some example code reviews:
* #283 (review)
* #284 (review)
This is built off a combination of previous code reviews, saved in
`docs/CODE_REVIEW_POSTMORTEM.md`, and the review rules in
`references/review-rules.md`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ddfc763 to
5cbcc13
Compare
…litting
- AdbRunner: check exit codes consistently in ListDevicesAsync, WaitForDeviceAsync,
StopEmulatorAsync, GetShellPropertyAsync, RunShellCommandAsync
- AdbRunner: bare catch {} → catch (Exception) in GetEmulatorAvdNameAsync
- AdbDeviceInfo.cs: split 3 public types into separate files (AdbDeviceType.cs, AdbDeviceStatus.cs)
- EnvironmentVariableNames: add AndroidAvdHome constant, use in AvdManagerRunner
- AvdManagerRunner.AvdManagerPath: delegate to ProcessUtils.FindCmdlineTool (remove duplicate logic)
- EmulatorRunner.StartAvd: additionalArgs string? → IEnumerable<string>? for proper arg separation
- EmulatorRunner.StartAvd: set RedirectStandardOutput/Error=false to prevent pipe deadlock
- EmulatorBootOptions.AdditionalArgs: string? → IEnumerable<string>?
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fb86451 to
0696fcc
Compare
Adds EmulatorRunner with StartAvd, ListAvdNamesAsync, and BootAndWaitAsync. Adds virtual shell methods to AdbRunner for testability. Adds ConfigureEnvironment to AndroidEnvironmentHelper. 212/213 tests pass (1 pre-existing JDK failure). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0696fcc to
7b95818
Compare
Summary
Wraps
emulatorCLI operations for starting and managing Android emulators. Addresses #278.Changes
EmulatorRunner — Emulator boot and management
New:
BootAndWaitAsync— Ports the boot emulator logic fromdotnet/android'sBootAndroidEmulatorMSBuild task into the shared library.Three-phase boot:
adb devices→ pass throughFull boot =
sys.boot_completed == "1"ANDpm path androidreturnspackage:prefix.New:
EmulatorBootResult— Result type withSuccess,Serial,ErrorMessageNew:
EmulatorBootOptions— Options type withBootTimeout,AdditionalArgs,ColdBoot,PollIntervalAdbRunner — Shell command helpers
New:
GetShellPropertyAsync— Runsadb -s serial shell getprop propertyNew:
RunShellCommandAsync— Runsadb -s serial shell commandThese support the boot wait logic (checking
sys.boot_completedandpm path android).API
Downstream Consumer
dotnet/androidBootAndroidEmulator.cswill delegate toEmulatorRunner.BootAndWaitAsync()via the submodule — similar to howGetAvailableAndroidDevicesdelegates toAdbRunner.ParseAdbDevicesOutput().Draft dotnet/android PR to follow.
Tests
13 unit tests (
EmulatorRunnerTests.cs):ParseListAvdsOutput: multiple AVDs, empty, Windows newlines, blank linesEmulatorPath: SDK discovery, missing SDK, null SDKAlreadyOnlineDevice_PassesThrough: device already in adb devicesAvdAlreadyRunning_WaitsForFullBoot: AVD running, wait for boot completionBootEmulator_AppearsAfterPolling: AVD not running, appears after pollingLaunchFailure_ReturnsError: emulator path missingBootTimeout_BootCompletedNeverReaches1: boot timeoutMultipleEmulators_FindsCorrectAvd: finds correct AVD among multipleTest scenarios ported from
dotnet/androidBootAndroidEmulatorTests.cs.Dependencies
Requires #283 (AdbRunner) — uses
ListDevicesAsync,GetEmulatorAvdNameAsync,GetShellPropertyAsync,RunShellCommandAsync.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com