Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 49 additions & 16 deletions .github/skills/update-androidsdk-packages/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ lives in two files:
Google republishes tool revisions on their own cadence; this skill brings those two files back in
sync with Google's *current stable* releases with a minimal, reviewable diff — matching the shape of PR #12371, which did exactly this (build-tools/platform-tools/cmdline-tools/cmake/emulator/sources/platform revisions all bumped, hashes recomputed, and per-arch macOS cmdline-tools support added when Apple Silicon archives showed up).

## Two hard rules — read these before touching anything
## Seven hard rules — read these before touching anything

**1. Never touch the Android NDK.** `_XAAndroidNdkRelease`, `_XAAndroidNdkPkgRevision`, and every
`XAAndroidNdkHash*` property in `Configuration.props`, plus the `android-ndk-r$(_XAAndroidNdkRelease)-*`
Expand All @@ -44,6 +44,37 @@ nothing else in the catalog needed updating this run. Silence here is a bug: the
rule is that a human decides whether/when to onboard a new API level, and they can't decide on
something they were never told about.

**3. Preserve the sources package for every shipped stable platform.** Source archives are additive,
not a single "latest" package: `sources;android-37.0` and `sources;android-37.1` install into distinct
SDK directories and both are needed when both platform levels are marked `IsLatestStable`. Never
replace or remove an existing `source-NN.N_rMM.zip` entry when a new stable platform source appears.
Keep one `_AndroidSdkPackage` entry and one version-specific hash property per stable platform level,
and preserve the full API level in `Destination` (`sources\android-37.0` uses the historical
`sources\android-37` directory; `sources\android-37.1` uses `sources\android-37.1`).

**4. Stable means Google's stable channel.** Never select an emulator or other tool from a
development, canary, beta, or preview channel merely because its revision sorts higher. Require
`channel-0` for emulator updates. If a package's channel metadata and release labeling disagree,
do not update it unattended; report the ambiguity instead.

**5. Command-line tools are a coordinated product dependency.** Do not update only the bootstrap
pins. A command-line-tools bump must also update
`src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.props` and the matching latest entry in
`src/Xamarin.Installer.AndroidSDK/Feeds/AndroidManifestFeed_d18.0.xml`, including every published
host/architecture archive. Confirm `CodeGenerator.targets` tracks the property file that supplies
`AndroidCommandLineToolsVersion`. If the automated workflow is not authorized to change every
required file, stop and report the coordinated update instead of opening a partial PR.

**6. Never execute an unverified downloader to accept licenses.** License acceptance must not run
the `android` bootstrapper, `sdkmanager`, or any payload fetched at execution time. Preserve all
existing valid fingerprints, add the pinned expected fingerprint under a cross-process lock, write
atomically, validate every line as a 40-character SHA-1 fingerprint, and create the acceptance
marker only after validation succeeds.

**7. Extraction outputs identify the exact archive.** Packages for different hosts or architectures
may share a destination. Their incremental output stamp must include both archive identity and
expected SHA-256; `source.properties` alone is not a safe extraction sentinel.

## Workflow

### 1. Read the current catalog
Expand All @@ -69,14 +100,11 @@ dotnet run .github/skills/update-androidsdk-packages/scripts/fetch_repo_package.

(These are C# file-based apps, matching the `ci_failures.cs` convention used by the `ci-status` skill — first run restores/builds, so allow a few extra seconds.)

The script sorts matches by revision (newest first) and flags anything whose path/display-name
looks like a preview build. **Treat that flag as a hint, not ground truth** — Google's
`channelRef` metadata is not a reliable stable/preview signal by itself (some genuinely-stable
packages carry a non-zero channel id, and freshly-promoted stable packages can briefly still show
old channel numbers). Cross-check the display name and version string yourself: a real stable
release reads like `36.0.1` or `28c`, not `37.0.0-rc1`, `2025.09.15-alpha01`, or anything with
`beta`/`canary`/`preview` in it. When genuinely unsure whether a release is stable, prefer the
previous confirmed-stable revision over guessing.
The script sorts matches by revision (newest first) and reports each package's channel. Require
`channel-0` for emulator updates and reject version/display names containing `alpha`, `beta`,
`canary`, `dev`, `preview`, or `rc`. For other package families, a non-zero channel or conflicting
metadata is ambiguous: keep the previous confirmed-stable revision and report it rather than
guessing.

Reference `references/package-catalog.md` for the mapping between each `androidsdk.targets` entry,
its manifest `path`, and its `Configuration.props` properties — it documents the current package
Expand Down Expand Up @@ -126,11 +154,15 @@ one shared macOS zip still covers both).
extension-level suffix too when Google has published one for an API level that already uses it
(e.g. `platform-34-ext7_r02` → `platform-34-ext12_r01`) — do not introduce an extension suffix for
an API level that never had one, or vice versa, without a clear reason from the manifest.
- The `source-NN_r0M.zip` sources package and `XAAndroidSourcesHash` should track whichever API level
is `IsLatestStable="true"` in `_PlatformPackage` (the `<Destination>` uses the integer API level,
e.g. `\sources\android-37`, even when the catalog entry is `platform-37.0_r01`). Update both the
zip name/Destination and the hash
together if the latest stable API level's source archive changed.
- Keep a `source-NN.N_r0M.zip` package for every API level marked `IsLatestStable` in
`_PlatformPackage`. Treat a newly published stable source as an addition, not a replacement.
Give each archive a version-specific hash property such as `XAAndroidSourcesHash37_0`, and use
the platform's distinct SDK directory as `Destination` (`37.0` historically maps to
`\sources\android-37`; `37.1` maps to `\sources\android-37.1`). Update an existing entry in place
only when Google publishes a newer revision for that same API level.
- When command-line tools changes, update the shipped product version and feed entry described in
hard rule 5 in the same change. Do not leave bootstrap and product dependency versions split.
- Keep archive/hash-specific extraction stamps intact when adding host or architecture variants.

### 5. Validate before finishing

Expand All @@ -153,8 +185,9 @@ dotnet build src/androidsdk/androidsdk.csproj --no-restore -v:minimal -t:_AddPla

Also check:
- **XML validity** — both edited files still parse (`dotnet build` will fail loudly on malformed XML, but a quick sanity check like `powershell -Command "[xml](Get-Content src/androidsdk/androidsdk.targets)"` catches issues faster).
- **Diff cleanliness** — `git status` and `git diff` should show changes *only* in `Configuration.props` and `src/androidsdk/androidsdk.targets`. This skill's scope is package pins, not the generated-package-xml template (`package.xml.in`) — if a routine refresh seems to require touching that file too, stop and flag it rather than including it, since automated runs of this skill (e.g. the `skill-runner` workflow) are only authorized to change the two files above. No stray temp files from hashing (the `sha256_of_url.cs` script cleans up after itself; double check if you downloaded anything manually instead).
- **The two hard rules above** — diff the NDK properties and the `_PlatformPackage` item count/API-level set against `git diff` to confirm neither was touched/expanded.
- **Diff cleanliness** — no unrelated files or stray temp downloads. Routine families remain scoped to `Configuration.props` and `src/androidsdk/androidsdk.targets`; command-line-tools updates additionally require the two shipped-product files in hard rule 5. Never edit `package.xml.in` during a routine refresh.
- **The seven hard rules above** — diff the NDK properties and the `_PlatformPackage` item count/API-level set, verify every `IsLatestStable` platform has its own sources package, verify selected releases are stable, and verify extraction outputs remain archive/hash-specific.
- **Command-line tools compatibility** — test with `licenses/android-sdk-license` and `.licenses-accepted` absent, with a pre-existing unrelated valid fingerprint, and with malformed content. Confirm the expected pinned fingerprint is created, the unrelated fingerprint is preserved, malformed content fails, writes are atomic/locked, and Gradle recognizes the Build Tools and platform licenses. No license-acceptance path may execute a network-capable Android CLI.
- **Formatting** — match the existing tab indentation and column alignment in both files (several `_PlatformPackage`/`_AndroidSdkPackage` lines are hand-aligned with extra spaces before `<ApiLevel>`/`<Hash>` — preserve that style rather than reformatting the whole block).

### 6. Summarize what changed
Expand Down
24 changes: 24 additions & 0 deletions .github/skills/update-androidsdk-packages/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@
"prompt": "our CI machines are on an old command-line tools version (19.0 / 13114758_latest) and I keep seeing warnings that a newer cmdline-tools package is available with separate mac_arm64/mac_x86_64 downloads now — can you get us current, and check whether Android platform 36.1 has a newer revision published too?",
"expected_output": "CommandLineToolsFolder/CommandLineToolsVersion bumped to the current stable release, with the command-line tools _AndroidSdkPackage split into a mac_x86_64 item (gated on _IsArm64Apple != 'true') and a new mac_arm64 item (gated on _IsArm64Apple == 'true') backed by a new XACmdlineToolsHashMacOSArm64 property, following the existing emulator/system-image pattern for _IsArm64Apple. The existing platform-36.1_r01 _PlatformPackage entry is checked against the manifest and its revision/hash updated in place if a newer one exists (no new API level added). Validation runs the BootstrapTasks build before the androidsdk.csproj build.",
"assertions": []
},
{
"id": 5,
"prompt": "Both platform 37.0 and 37.1 are marked IsLatestStable in androidsdk.targets. Refresh the Android sources packages to the current revisions.",
"expected_output": "Both source-37.0 and source-37.1 _AndroidSdkPackage entries remain present with separate version-specific SHA-256 properties. The 37.0 package installs to sources/android-37 and the 37.1 package installs to sources/android-37.1. Updating 37.1 never removes or overwrites the 37.0 package.",
"assertions": []
},
{
"id": 6,
"prompt": "Update command-line tools from 19.0 to a release where sdkmanager says --licenses is deprecated and exits successfully without creating the SDK license files.",
"expected_output": "Bootstrap pins, Xamarin.Installer.Common.props, and the installer manifest feed are updated together for every host/architecture. License acceptance invokes neither sdkmanager nor the network-capable Android CLI bootstrapper: it atomically adds the pinned expected fingerprint under a cross-process lock while preserving existing valid fingerprints and rejecting malformed files. Validation starts without either license file, then repeats with an unrelated valid fingerprint and malformed content, and confirms Gradle accepts the pinned Build Tools and platform licenses.",
"assertions": []
},
{
"id": 7,
"prompt": "The manifest lists emulator 37.2.5 on channel-2 and emulator 37.1.11 on channel-0. Refresh us to the newest emulator.",
"expected_output": "Emulator 37.1.11 is selected because channel-0 is required for emulator updates. The newer 37.2.5 development-channel package is explicitly rejected. All four stable host/architecture archives are hashed and pinned.",
"assertions": []
},
{
"id": 8,
"prompt": "Add a new macOS arm64 command-line-tools archive. It extracts to the same SDK directory as the x64 archive, so source.properties can stay as the shared output stamp.",
"expected_output": "The arm64 archive may share the destination, but source.properties is rejected as the sole incremental output. Extraction uses a stamp containing archive identity and expected SHA-256 so switching architectures or hashes forces replacement with the correct package.",
"assertions": []
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ drift as the skill is used.
|---|---|---|---|---|
| build-tools | `build-tools` | `XABuildToolsVersion`, `XABuildToolsFolder` | `XABuildToolsHashMacOS/Linux/Windows` | `build-tools_r$(XABuildToolsVersion)_{macosx,linux,windows}.zip` |
| platform-tools | `platform-tools` | `XAPlatformToolsVersion` | `XAPlatformToolsHashMacOS/Linux/Windows` | `platform-tools_r$(XAPlatformToolsVersion)-{darwin,linux,win}.zip` |
| cmdline-tools | `cmdline-tools` | `CommandLineToolsFolder`, `CommandLineToolsVersion` | `XACmdlineToolsHashMacOS`, `XACmdlineToolsHashMacOSArm64`, `XACmdlineToolsHashLinux/Windows` | `commandlinetools-{mac_x86_64,mac_arm64,linux,win}-$(CommandLineToolsVersion).zip` — macOS is arch-split; other hosts are one zip |
| cmdline-tools | `cmdline-tools` | `CommandLineToolsFolder`, `CommandLineToolsVersion`; shipped `AndroidCommandLineToolsVersion` in `src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.props` | `XACmdlineToolsHashMacOS`, `XACmdlineToolsHashMacOSArm64`, `XACmdlineToolsHashLinux/Windows` | `commandlinetools-{mac_x86_64,mac_arm64,linux,win}-$(CommandLineToolsVersion).zip`; also update the latest entry in `src/Xamarin.Installer.AndroidSDK/Feeds/AndroidManifestFeed_d18.0.xml` |
| cmake | `cmake;` | `AndroidCmakeVersion` | `XACmakeHashMacOS/Linux/Windows` | `cmake-$(AndroidCmakeVersion)-{darwin,linux,windows}.zip` |
| emulator | `emulator` | `EmulatorVersion`, `EmulatorPkgRevision` | `XAEmulatorHashMacOSx64`, `XAEmulatorHashMacOSArm64`, `XAEmulatorHashLinux/Windows` | `emulator-{darwin_x64,darwin_aarch64,linux_x64,windows_x64}-$(EmulatorVersion).zip`; also drives a synthesized `package.xml` via `package.xml.in` |
| API 29 system image | `sys-img/android` manifest, `path="system-images;android-29;default;{x86_64,arm64-v8a}"` | (fixed `x86_64-29_r08*`/`arm64-v8a-29_r08` filenames — check manifest for a newer `rNN` if refreshing) | `XASystemImageHashMacOSx64/MacOSArm64/Linux/Windows` | `{x86_64,arm64-v8a}-29_r08{-darwin,-linux,-windows,}.zip` under `sys-img/android/` |
| m2repository | `extras;android;m2repository` | (embedded in filename, e.g. `_r47`) | `XAAndroidM2RepositoryHash` | `android_m2repository_r47.zip`, host-agnostic |
| docs | `docs` | (embedded in filename, e.g. `-24_r01`) | `XAAndroidDocsHash` | `docs-24_r01.zip`, host-agnostic |
| sources | `sources;android-NN` (tracks the latest stable platform) | (embedded in filename) | `XAAndroidSourcesHash` | `source-<latest-stable-api>_r0M.zip`, `Destination` embeds the API level too |
| platform APIs | `platforms;android-NN` | n/a — `_PlatformPackage` item's `Include` *is* the version string | `Hash` metadata per `_PlatformPackage` item | `_PlatformPackage` item group near the top of the file; one `IsLatestStable="true"` entry drives default install + the sources package above |
| sources | `sources;android-NN.N` (one per stable shipped platform) | (embedded in filename) | Version-specific, e.g. `XAAndroidSourcesHash37_0` | One `source-NN.N_r0M.zip` entry per `IsLatestStable` platform; preserve each distinct `Destination` (`37.0` historically uses `sources\android-37`, while `37.1` uses `sources\android-37.1`) |
| platform APIs | `platforms;android-NN` | n/a — `_PlatformPackage` item's `Include` *is* the version string | `Hash` metadata per `_PlatformPackage` item | `_PlatformPackage` item group near the top of the file; every `IsLatestStable="true"` entry drives default install and requires a corresponding sources package |
| **Android NDK — OUT OF SCOPE** | `ndk` | `_XAAndroidNdkRelease`, `_XAAndroidNdkPkgRevision` | `XAAndroidNdkHashMacOS/Linux/Windows` | `android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip` — **never edit as part of this skill** |

## Notes on Apple Silicon archives
Expand All @@ -30,6 +30,18 @@ an `'$(_IsArm64Apple)' != 'true'` item for the existing x86_64/generic macOS arc
property. Don't add an arm64-specific branch speculatively for families where Google still ships
one universal/x86_64-only macOS archive.

Every extraction output stamp must include the selected archive identity and SHA-256. Multiple
architectures can intentionally share a destination, so `source.properties` alone must never be
used as the incremental output.

## Notes on stable channels and licenses

Emulator updates must come from `channel-0`; a higher development-channel revision is not stable.
Command-line-tools updates are incomplete unless bootstrap pins, shipped product versions, and feed
archives all move together. License acceptance must remain deterministic and offline: preserve
valid existing fingerprints and atomically add the pinned expected SHA-1 under a cross-process
lock. Never invoke the command-line tools' `android` bootstrapper or another mutable downloader.

## Notes on platform extension levels

Some platform API levels ship as a numbered "extension" (e.g. `platform-34-ext12_r01`) rather than
Expand Down
Loading
Loading