You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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:
Enumerate every cmdline-tools/<directory> candidate that contains the requested executable.
Read Pkg.Revision from <directory>/source.properties.
Select the highest actual revision, with a stable release preferred over a prerelease at the same core version.
Use deterministic directory-name parsing only as a fallback when Pkg.Revision is missing or invalid.
Preserve an explicitly documented legacy tools/bin fallback only where the legacy command contract is compatible.
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
Bootstrap an empty SDK using Xamarin.Android.Tools.SdkManager.BootstrapAsync() and the default manifest feed.
Observe that cmdline-tools/19.0 is installed because d18-0 still advertises revision 19 as latest.
Run that manager's sdkmanager --list; observe cmdline-tools;latest | 22.0 in Available Packages.
Run sdkmanager "cmdline-tools;latest", producing cmdline-tools/latest with Pkg.Revision=22.0.
Call SdkManager.FindSdkManagerPath(); it still selects the numeric 19.0 directory because numeric directories outrank latest.
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)
Android framework version
net11.0-android (Preview)
Affected platform version
dotnet/androidmain (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.DefaultManifestFeedUrlpoints tohttps://aka.ms/AndroidManifestFeed/d18-0.(latest)withfilesystem-path="cmdline-tools/latest".SdkManager.BootstrapAsync()ignores that filesystem path and extracts the archive tocmdline-tools/<revision>, so a clean bootstrap createscmdline-tools/19.0.cmdline-tools;latestas 22.0.SdkManager.FindSdkManagerPath()searches numeric directories first andlatestlast, whileProcessUtils.FindCmdlineTool()searcheslatestfirst and then numeric directories. See Align FindSdkManagerPath with FindCmdlineTool discovery order #12072.latestis only an alias, directory names can be stale or misleading, and multiple numeric/preview directories can coexist. The authoritative version isPkg.Revisionin each candidate'ssource.properties.sdkmanager --list_installed, so callers cannot reliably discover the installed manager revision from package-list output.As a result, installing
cmdline-tools;latestcan leave an older numeric directory selected for subsequentsdkmanageroravdmanageroperations. With API 37's dottedAndroidVersion.ApiLevel=37.0metadata, Tools 19 generated AVD metadata withtarget=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.AndroidSdkand use it forsdkmanager,avdmanager, and future tools:cmdline-tools/<directory>candidate that contains the requested executable.Pkg.Revisionfrom<directory>/source.properties.Pkg.Revisionis missing or invalid.tools/binfallback only where the legacy command contract is compatible.Also provide an ensure/update operation suitable for higher-level consumers:
cmdline-tools;latestcatalog entry;cmdline-tools;latestonly when the manager is missing or stale;sdkmanager --update, because that updates every installed Android SDK package;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()andProcessUtils.FindCmdlineTool()use the same revision-aware resolver.cmdline-tools/19.0pluscmdline-tools/latestwithPkg.Revision=22.0selectslatest.cmdline-tools/22.0plus stalelatestwithPkg.Revision=19.0selects the numeric directory.Pkg.Revisionare resolved usingPkg.Revision.source.propertieshas deterministic fallback behavior.sdkmanager --list_installed.Steps to Reproduce
Xamarin.Android.Tools.SdkManager.BootstrapAsync()and the default manifest feed.cmdline-tools/19.0is installed because d18-0 still advertises revision 19 as latest.sdkmanager --list; observecmdline-tools;latest | 22.0in Available Packages.sdkmanager "cmdline-tools;latest", producingcmdline-tools/latestwithPkg.Revision=22.0.SdkManager.FindSdkManagerPath(); it still selects the numeric19.0directory because numeric directories outranklatest.avdmanagerto create an Android API 37 AVD. The older parser can generatetarget=android-0from the valid dotted37.0metadata.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
latestpackage 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