[Xamarin.Android.Tools.AndroidSdk] Parse the new Android CLI sdkmanager --list format - #12466
Conversation
…ger --list` format
## Issue
`sdkmanager` is deprecated in cmdline-tools 23+ and replaced by the new "Android
CLI", whose `--list` output has a different shape than the classic tool:
* a multi-line deprecation banner precedes the sections;
* packages are printed as whitespace-aligned columns instead of the classic
`path | version | description` pipe-delimited table; and
* the section header is lowercase `Available packages:` (the classic tool
emitted `Available Packages:`).
`ParseSdkManagerList` only understood the pipe-delimited table. Against the new
tool it split each package row on `|`, got a single column, decided the row had
too few fields, and skipped it -- so *every* package line was dropped and the
returned installed-package list came back empty even though packages were
installed. The lowercase `Available packages:` header was additionally missed
because the header comparison was case-sensitive.
## Impact on the .NET MAUI VS Code extension
This surfaced as a user-visible bug in the .NET MAUI extension for VS Code, which
is how it was found. To decide whether the Android emulator/AVD prerequisites are
satisfied, the extension asks the MAUI CLI to check installed Android SDK
components, and the CLI uses this library to parse `sdkmanager --list`. With the
empty parse result the extension concluded that no system images were installed
and showed a false "AVD missing components" warning in the Android language
status item, offering an "Install" action. Running that check reported success,
yet the warning never cleared -- because the underlying `--list` parse kept
returning nothing on the new Android CLI. Users on cmdline-tools 23+ hit this even
with a fully configured SDK.
## Fix
Both changes are backward-compatible with the classic tool:
* when a package row contains no `|`, split it on runs of two-or-more spaces
(the new whitespace-aligned columns), keeping the pipe split for classic
output; and
* match the section headers case-insensitively so `Available packages:` is
recognized regardless of casing.
Added an NUnit regression test built from real Android CLI output (deprecation
banner + lowercase header + whitespace columns, including a system-image id with
an internal `/` and a multi-word "16 KB Page Size ..." description) asserting that
both installed and available packages parse correctly.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05e54498-1ff0-4f5c-8d8d-0edcf8526596
There was a problem hiding this comment.
Pull request overview
This PR updates Xamarin.Android.Tools.AndroidSdk’s sdkmanager --list parsing to handle the newer Android CLI output format (cmdline-tools 23+), where package rows are whitespace-column aligned (no |) and some section headers differ in casing. This prevents installed/available package detection from incorrectly returning empty results on newer SDK toolchains (notably impacting downstream tooling like MAUI’s VS Code extension).
Changes:
- Make section header detection case-insensitive so
Available packages:is recognized reliably. - Parse package rows that have no
|by splitting on runs of 2+ whitespace to support the Android CLI column format. - Add an NUnit regression test covering the Android CLI banner + lowercase header + whitespace columns.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs |
Adds Android CLI-compatible parsing by case-insensitive header matching and whitespace-column splitting fallback. |
tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs |
Adds a regression test validating parsing of installed/available packages from real-world Android CLI-style output. |
Suppressed comments (3)
tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs:263
- Avoid using the null-forgiving operator (!) in tests; use an explicit null check (or ?? throw) to keep the test flow-safe.
var sysImg361 = installed.FirstOrDefault (p => p.Path == "system-images/android-36.1/google_apis/arm64-v8a");
Assert.IsNotNull (sysImg361);
Assert.AreEqual ("4.0.0", sysImg361!.Version);
tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs:269
- Avoid using the null-forgiving operator (!) in tests; prefer an explicit null check (or ?? throw) before dereferencing.
var googleApis = available.FirstOrDefault (p => p.Path == "add-ons/addon-google_apis-google-10");
Assert.IsNotNull (googleApis);
Assert.AreEqual ("2.0.0", googleApis!.Version);
Assert.IsFalse (googleApis.IsInstalled);
tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs:258
- Avoid using the null-forgiving operator (!) in tests; use a null check that makes the failure explicit before dereferencing.
var sysImg = installed.FirstOrDefault (p => p.Path == "system-images/android-37.0/google_apis_ps16k/arm64-v8a");
Assert.IsNotNull (sysImg, "16 KB page-size system image should be parsed");
Assert.AreEqual ("6.0.0", sysImg!.Version);
Assert.AreEqual ("16 KB Page Size Google APIs ARM 64 v8a System Image", sysImg.Description);
jonathanpeppers
left a comment
There was a problem hiding this comment.
Overall, I'm OK to do a one-time fix, but the true fix here would be to create a new Java project that imports SDK manager library from maven and make it able to output something more parseable like JSON. There is not a switch for doing this with sdkmanager.
Here are details on how that could work:
Decompiled Command-line Tools **19.0**:
- `ListAction` loads a typed `RepositoryPackages` model.
- It exposes `getLocalPackages()`, `getRemotePackages()`, `getNewPkgs()`, and `getUpdatedPkgs()`.
- The model is then hard-wired into `TableFormatter`; no hidden JSON/XML switch exists.
**Best option:** write a small Java shim in package `com.android.sdklib.tool.sdkmanager`, reuse `SdkManagerCliSettings`, execute the load, then serialize `RepositoryPackages` with bundled Gson. This preserves channels, custom sources, obsolete filtering, proxies, and SDK parsing without scraping output. It does rely on internal APIs and may need updates with future command-line tools.
Other options:
1. **Installed packages only:** parse each `**\package.xml`. This is structured XML, but older/corrupt packages may require `source.properties` fallback.
2. **CLI parsing:** non-verbose output is explicitly joined with `" | "`, but values are not escaped.
3. **Remote XML directly:** possible, but undesirable because packages come from multiple repository feeds and require channel/host filtering.
The Java shim is the cleanest machine-readable solution if this needs to be reliable.|
@mauroa another idea, there is an We could lean into using that more. |
Reuse parser metadata and the whitespace separator regex, and avoid new null-forgiving operators in the regression test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a292f53-56d7-4219-9179-21db5b2fc4f1
Cover whitespace-formatted updates and case-insensitive matching for every package section header. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a292f53-56d7-4219-9179-21db5b2fc4f1
|
Future .NET 12 investigation is tracked in #12472. |
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
❌ Reject
Findings: 1 error · 0 warnings · 0 suggestions.
The parser correctly recognizes the new whitespace table and preserves classic pipe-delimited behavior, with focused coverage for sections and updates. However, the new CLI emits slash-form package IDs while the existing command-line-tools update path requires canonical semicolon IDs, so the parsed result breaks EnsureLatestCommandLineToolsAsync on the very cmdline-tools versions this PR targets.
CI is still in progress; the completed Android Tools Tests Mac and CLA checks are passing.
Generated by Android PR Reviewer for #12466 · gpt56 · 75.6 AIC · ⌖ 8.89 AIC · ⊞ 25.7K
Comment /review to run again
Recognize both sdkmanager's semicolon package ID and Android CLI's slash package ID when resolving the latest command-line tools package. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8a292f53-56d7-4219-9179-21db5b2fc4f1
Issue
sdkmanageris deprecated in cmdline-tools 23+ and replaced by the new "Android CLI", whose--listoutput differs from the classic tool:path | version | descriptionpipe-delimited table;Available packages:instead ofAvailable Packages:; andcmdline-tools/latest, instead of the classiccmdline-tools;latestform.ParseSdkManagerListonly understood the pipe-delimited table and case-sensitive headers. Against the new output, every package row was skipped and the installed-package list was empty. Additionally, the slash-form command-line-tools ID was not recognized byEnsureLatestCommandLineToolsAsync.Impact on the .NET MAUI VS Code extension
The .NET MAUI extension for VS Code uses this library through the MAUI CLI to check Android emulator/AVD prerequisites. The empty parse result caused a false "AVD missing components" warning even on fully configured SDKs. Running the offered install action reported success, but the warning remained because subsequent checks still parsed no packages.
Fix
The current implementation remains compatible with both output formats:
sdkmanageroutput;cmdline-tools;latestandcmdline-tools/latestwhen resolving the latest command-line-tools package, without globally rewriting package IDs.Tests
Expanded NUnit coverage verifies:
/and descriptions containing spaces; andEnsureLatestCommandLineToolsAsyncfor both command-line-tools ID formats.Follow-up
A structured, maintainable package-discovery approach for .NET 12 is tracked by #12472. That investigation covers a Java wrapper around the SDK manager libraries and possible direct Android CLI integration.