Skip to content

Resolve and refresh Android command-line tools by installed Pkg.Revision #12137

Description

@rmarinho

Android framework version

net11.0-android (Preview)

Affected platform version

dotnet/android main (Xamarin.Android.Tools.AndroidSdk) as of 2026-07-16; .NET 11 Preview 6; Android API 37; Android SDK Command-line Tools 19.0 through 22.0.

Description

The Android command-line-tool bootstrap, discovery, and update paths currently disagree about which installed toolset is active, and none of them compare the actual installed revision from source.properties.

This became a correctness issue with Android API 37:

  • SdkManager.DefaultManifestFeedUrl points to https://aka.ms/AndroidManifestFeed/d18-0.
  • The live feed was generated on 2025-01-28 and still marks Command-line Tools 19.0 as (latest) with filesystem-path="cmdline-tools/latest".
  • SdkManager.BootstrapAsync() ignores that filesystem path and extracts the archive to cmdline-tools/<revision>, so a clean bootstrap creates cmdline-tools/19.0.
  • Google's current SDK catalog advertises cmdline-tools;latest as 22.0.
  • SdkManager.FindSdkManagerPath() searches numeric directories first and latest last, while ProcessUtils.FindCmdlineTool() searches latest first and then numeric directories. See Align FindSdkManagerPath with FindCmdlineTool discovery order #12072.
  • Changing precedence alone is insufficient: latest is only an alias, directory names can be stale or misleading, and multiple numeric/preview directories can coexist. The authoritative version is Pkg.Revision in each candidate's source.properties.
  • Command-line Tools 19 also omits its own command-line-tools package from sdkmanager --list_installed, so callers cannot reliably discover the installed manager revision from package-list output.

As a result, installing cmdline-tools;latest can leave an older numeric directory selected for subsequent sdkmanager or avdmanager operations. With API 37's dotted AndroidVersion.ApiLevel=37.0 metadata, Tools 19 generated AVD metadata with target=android-0; newer command-line tools preserve API 37 correctly.

Proposed ownership and behavior

Add one shared command-line-tool resolver in Xamarin.Android.Tools.AndroidSdk and use it for sdkmanager, avdmanager, and future tools:

  1. Enumerate every cmdline-tools/<directory> candidate that contains the requested executable.
  2. Read Pkg.Revision from <directory>/source.properties.
  3. Select the highest actual revision, with a stable release preferred over a prerelease at the same core version.
  4. Use deterministic directory-name parsing only as a fallback when Pkg.Revision is missing or invalid.
  5. Preserve an explicitly documented legacy tools/bin fallback only where the legacy command contract is compatible.
  6. Expose the selected path and revision, or otherwise expose installed command-line-tool candidates, so consumers do not need to duplicate filesystem scanning.

Also provide an ensure/update operation suitable for higher-level consumers:

  • bootstrap when no manager exists;
  • compare the selected installed revision with Google's cmdline-tools;latest catalog entry;
  • install cmdline-tools;latest only when the manager is missing or stale;
  • re-resolve the active manager after installation;
  • do not use sdkmanager --update, because that updates every installed Android SDK package;
  • propagate cancellation and progress.

This allows the existing d18-0 bootstrap to provide the initial runnable manager, after which that manager can conditionally install Google's current command-line tools.

Acceptance criteria

  • SdkManager.FindSdkManagerPath() and ProcessUtils.FindCmdlineTool() use the same revision-aware resolver.
  • cmdline-tools/19.0 plus cmdline-tools/latest with Pkg.Revision=22.0 selects latest.
  • cmdline-tools/22.0 plus stale latest with Pkg.Revision=19.0 selects the numeric directory.
  • Directory names that disagree with Pkg.Revision are resolved using Pkg.Revision.
  • Missing/malformed source.properties has deterministic fallback behavior.
  • Stable-versus-prerelease ordering is covered.
  • Conditional refresh is covered for missing, stale, and already-current installations.
  • Existing callers can obtain the selected revision without parsing sdkmanager --list_installed.

Steps to Reproduce

  1. Bootstrap an empty SDK using Xamarin.Android.Tools.SdkManager.BootstrapAsync() and the default manifest feed.
  2. Observe that cmdline-tools/19.0 is installed because d18-0 still advertises revision 19 as latest.
  3. Run that manager's sdkmanager --list; observe cmdline-tools;latest | 22.0 in Available Packages.
  4. Run sdkmanager "cmdline-tools;latest", producing cmdline-tools/latest with Pkg.Revision=22.0.
  5. Call SdkManager.FindSdkManagerPath(); it still selects the numeric 19.0 directory because numeric directories outrank latest.
  6. Use that selected avdmanager to create an Android API 37 AVD. The older parser can generate target=android-0 from the valid dotted 37.0 metadata.

Expected: all Android SDK command-line tools resolve to the highest actual installed revision, and a higher-level ensure operation updates only when stale.

Actual: discovery depends on conflicting folder-precedence rules, and the bootstrap can leave Tools 19 active even after a current latest package is present.

Did you find any workaround?

The VS Code .NET MAUI extension currently scans every cmdline-tools/*/source.properties, mirrors the MAUI CLI's numeric-first selection, compares the active revision with Google's catalog, and installs the matching numeric package so the old resolver will select it. It also repairs already-corrupted API 37 AVD metadata. This is a tactical compatibility layer that should be removed after the shared Android tooling owns revision discovery and conditional refresh.

Relevant log output

# Live Microsoft bootstrap feed
<cmdline-tools revision="19.0" path="cmdline-tools;19.0" filesystem-path="cmdline-tools/latest" ... description="Android SDK Command-line Tools (latest)" ...>

# Command-line Tools 19: installed-package output omits command-line tools
Info: Parsing legacy package: .../cmdline-tools/19.0
Installed packages:

# The same Tools 19 catalog reports a newer current package
Available Packages:
cmdline-tools;21.0   | 21.0 | Android SDK Command-line Tools
cmdline-tools;22.0   | 22.0 | Android SDK Command-line Tools
cmdline-tools;latest | 22.0 | Android SDK Command-line Tools (latest)

Metadata

Metadata

Assignees

No one assigned

    Labels

    android-toolsIssues migrated from dotnet/android-tools / relates to the xamarin-android-tools subtreeneeds-triageIssues that need to be assigned.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions