Skip to content

[Xamarin.Android.Tools.AndroidSdk] Parse the new Android CLI sdkmanager --list format - #12466

Merged
jonathanpeppers merged 4 commits into
mainfrom
dev/maagno/sdkparsingissue
Aug 24, 2026
Merged

[Xamarin.Android.Tools.AndroidSdk] Parse the new Android CLI sdkmanager --list format#12466
jonathanpeppers merged 4 commits into
mainfrom
dev/maagno/sdkparsingissue

Conversation

@mauroa

@mauroa mauroa commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Issue

sdkmanager is deprecated in cmdline-tools 23+ and replaced by the new "Android CLI", whose --list output differs from 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;
  • section-header casing differs, such as Available packages: instead of Available Packages:; and
  • package IDs use slash form, such as cmdline-tools/latest, instead of the classic cmdline-tools;latest form.

ParseSdkManagerList only 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 by EnsureLatestCommandLineToolsAsync.

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:

  • use the existing pipe-delimited parsing for classic sdkmanager output;
  • split Android CLI rows on runs of two or more whitespace characters using a shared compiled regex;
  • match package section headers case-insensitively from a shared section definition list; and
  • recognize both cmdline-tools;latest and cmdline-tools/latest when resolving the latest command-line-tools package, without globally rewriting package IDs.

Tests

Expanded NUnit coverage verifies:

  • classic pipe-delimited installed, available, and update sections;
  • Android CLI whitespace-delimited installed, available, and update sections;
  • case-insensitive recognition of every section header;
  • package IDs containing / and descriptions containing spaces; and
  • end-to-end parsing into EnsureLatestCommandLineToolsAsync for 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.


  • Useful description of why the change is necessary.
  • Links to issues fixed or follow-up work
  • Unit tests

…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
@mauroa
mauroa requested review from rmarinho and a lite review from Copilot August 20, 2026 18:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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);

Comment thread src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs Outdated
Comment thread tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs Outdated

@jonathanpeppers jonathanpeppers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs Outdated
@jonathanpeppers

Copy link
Copy Markdown
Member

@mauroa another idea, there is an android CLI now.

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
@jonathanpeppers

Copy link
Copy Markdown
Member

Future .NET 12 investigation is tracked in #12472.

@jonathanpeppers

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12466

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ 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

Comment thread src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs
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
@jonathanpeppers jonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Aug 21, 2026
@jonathanpeppers
jonathanpeppers enabled auto-merge (squash) August 21, 2026 16:52
@jonathanpeppers
jonathanpeppers merged commit 399c302 into main Aug 24, 2026
44 checks passed
@jonathanpeppers
jonathanpeppers deleted the dev/maagno/sdkparsingissue branch August 24, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants