[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer - #11636

Merged
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Jun 24, 2026
Merged

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers merged 10 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Context

Today, dotnet/android provisions the .NET SDK with bespoke C# code in
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.cs
(220 lines) — fetching dotnet-install.{sh,ps1} from a hard-coded URL,
running it with config-driven args, and supporting a --with-archive
override for offline scenarios. Other .NET repos (dotnet/sdk,
dotnet/runtime, dotnet/aspnetcore) all use Arcade's standard
eng/common/dotnet-install.{sh,ps1} flow — there's no reason for us to
maintain a custom one.

Phase 1 of a longer migration

This PR is the SDK-provisioning slice of a larger effort to delete
xaprepare entirely
so the build collapses to:

./eng/install-dotnet.sh # one-time bootstrap
dotnet build Xamarin.Android.sln # everything else

xaprepare today is 333 KB / 116 files but only 4 step files have real
logic (Step_PrepareDotNetWorkloads, Step_GenerateFiles,
Step_GenerateFiles.Windows, Step_GenerateCGManifest). Once each step
has an MSBuild equivalent, the surrounding 332 KB of plumbing
(Application/, ToolRunners/, OperatingSystems/) can also be
deleted. Follow-up PRs are planned for each remaining step.

What changes here

New: eng/install-dotnet.{sh,ps1}

Thin bootstrap wrappers that:

  1. Read <MicrosoftNETSdkPackageVersion> from eng/Versions.props
    (single source of truth, kept up to date by darc when
    Microsoft.NET.Sdk flows from dotnet/dotnet).
  2. Download Microsoft's official dotnet-install.{sh,ps1} from
    https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached
    under bin/$Configuration/dotnet/).
  3. Invoke it with --version <pinned> and --install-dir bin/$Configuration/dotnet.

Install location stays at bin/$Configuration/dotnet/ (where xaprepare
put it) so dotnet-local.{cmd,sh} continues to work unchanged.

Wired in everywhere xaprepare ran the install before

  • Makefile: prepare: target now depends on a new install-dotnet
    target that calls ./eng/install-dotnet.sh.
  • build-tools/scripts/PrepareWindows.targets: new _InstallDotNet
    target runs eng/install-dotnet.ps1 before _BuildXAPrepare.
  • build.cmd: unchanged — the existing dotnet msbuild ... -t:Prepare
    flow still works because _BuildXAPrepare now installs the SDK first.

Step_InstallDotNetPreviewStep_PrepareDotNetWorkloads

The old 220-line installer step is deleted. A new ~120-line
Step_PrepareDotNetWorkloads.cs replaces it and only does
Android-specific workload prep (NuGet cleanup, package-download.proj
restore with 3-attempt retry, and workload manifest copy). Everything
SDK-install-related (download script, archive override,
InstallDotNetAsync etc.) is gone.

global.json:tools.dotnetNOT added

I originally tried pinning the SDK version in global.json:tools.dotnet
(the standard Arcade convention), but verified in
arcade-services/.../DependencyFileManager.cs that darc
does not auto-update global.json:tools.dotnet
when the
Microsoft.NET.Sdk asset flows. Only specific Arcade/Helix SDK names
and the literal name dotnet are special-cased. So a tools.dotnet
pin would have permanently drifted from the auto-flowed
eng/Versions.props:MicrosoftNETSdkPackageVersion.

The wrappers therefore read the version from Versions.props directly
and bypass Arcade's eng/common/tools.{sh,ps1} (which would otherwise
strict-mode-read $GlobalJson.tools). Single source of truth = the
darc-flowed eng/Versions.props.

Other cleanups

  • Configurables.{Unix,Windows}.cs: removed Urls.DotNetInstallScript
    (no longer needed).
  • Context.cs + Main.cs: removed LocalDotNetSdkArchive /
    --dotnet-sdk-archive plumbing. (The replacement is the standard
    DOTNET_INSTALL_DIR env var that anyone needing offline support can
    set themselves.)

Verified on Windows

ActionTime
Cold eng/install-dotnet.ps1 (with download)~12s
Warm re-run (idempotent fast path)~2.5s
Full dotnet msbuild Xamarin.Android.sln -t:Prepare~88s

The dotnet --list-sdks output after a cold install correctly shows
11.0.100-preview.5.26268.112 at
bin/Debug/dotnet/sdk. Re-running Prepare is silent (no spurious
re-installs, no extra workload restores).

Migration path for the rest of xaprepare (future PRs)

StepMigration target
Step_PrepareDotNetWorkloadsMSBuild .targets file
Step_GenerateCGManifestCI yaml step or .targets file
Step_GenerateFiles[.Windows]Per-file MSBuild targets with Inputs/Outputs
(everything)Delete build-tools/xaprepare/ and PrepareWindows.targets

End state: ./eng/install-dotnet.sh + dotnet build. Nothing else.

jonathanpeppersand others added 3 commits June 11, 2026 10:08
Replace xaprepare's bespoke `dotnet-install` invocation with Arcade's
standard `eng/common/tools.{sh,ps1}` bootstrap, matching dotnet/sdk,
dotnet/runtime, and dotnet/aspnetcore.
* `global.json`: pin `tools.dotnet` so Arcade's `InitializeDotNetCli`
knows which SDK to install. darc auto-updates this whenever
`Microsoft.NET.Sdk` flows from dotnet/dotnet via the existing
Maestro subscription.
* `eng/install-dotnet.{sh,ps1}`: thin wrappers that set
`DOTNET_INSTALL_DIR=DOTNET_GLOBAL_INSTALL_DIR=bin/$(Configuration)/dotnet/`
(preserving the existing install location) and call
`InitializeDotNetCli` from `eng/common/tools.{sh,ps1}`.
* `Makefile`: `prepare` now depends on a new `install-dotnet` target
that runs `./eng/install-dotnet.sh` first.
* `build-tools/scripts/PrepareWindows.targets`: add an
`_InstallDotNet` target that invokes `eng/install-dotnet.ps1`
before `_BuildXAPrepare`, so `dotnet msbuild Xamarin.Android.sln
-t:Prepare` (used on Windows CI) is self-bootstrapping.
* `Step_InstallDotNetPreview.cs` is deleted and replaced by
`Step_PrepareDotNetWorkloads.cs`. The new step assumes the SDK
is already installed at `bin/$(Configuration)/dotnet/` and only
performs the Android-specific workload prep:
* Cleans stale Mono Android runtime/workload NuGet directories.
* Restores `package-download.proj` (Mono runtime packs +
Mono/Emscripten workload manifest packages).
* Copies the workload manifests into the local SDK's
`sdk-manifests/`.
* Removes obsolete configuration:
* `Configurables.Urls.DotNetInstallScript` (Unix and Windows)
* `--dotnet-sdk-archive` xaprepare option and its
`Context.LocalDotNetSdkArchive` plumbing
* `DownloadDotNetInstallScript`, `GetInstallationScriptArgs`,
`InstallDotNetAsync`, `InstallDotNetFromLocalArchiveAsync`
methods (~150 lines of bespoke install logic).
The SDK install location stays at `bin/$(Configuration)/dotnet/`,
so `dotnet-local.{cmd,sh}` and other consumers continue to work
without changes. CI's `use-dot-net.yaml` is unchanged: it still
provisions a system .NET to bootstrap xaprepare; the pinned preview
SDK install simply moves from xaprepare to Arcade.
Verified locally on Windows: `dotnet msbuild Xamarin.Android.sln
-t:Prepare` after `git clean -xdf bin/Debug/dotnet/` installs the
pinned 11.0.100-preview.5.26268.112 SDK and copies the Mono +
Emscripten workload manifests into `sdk-manifests/`.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
darc does not auto-update global.json:tools.dotnet when Microsoft.NET.Sdk
flows from dotnet/dotnet (verified in arcade-services
DependencyFileManager.cs: only Microsoft.DotNet.Arcade.Sdk, the
*.SharedFramework.Sdk family, Microsoft.DotNet.CMake.Sdk,
Microsoft.NET.Sdk.IL, and the literal name "dotnet" are special-cased).
Pinning the SDK version in global.json would have permanently drifted
from the auto-flowed eng/Versions.props value. Read the version directly
from eng/Versions.props instead, making it the single source of truth.
eng/install-dotnet.{sh,ps1} now download Microsoft's official
dotnet-install.{sh,ps1} from
https://builds.dotnet.microsoft.com/dotnet/scripts/v1/ (cached under
bin/$Configuration/dotnet/) and invoke it with the version parsed from
eng/Versions.props:MicrosoftNETSdkPackageVersion. This bypasses Arcade's
eng/common/tools.{sh,ps1} (which strict-mode-reads $GlobalJson.tools)
and lets us drop the tools.dotnet pin from global.json entirely.
Verified on Windows:
- cold install: ~12s
- warm re-run: ~2.5s (idempotent fast path)
- full Prepare: ~88s
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI failed with "Permission denied" when `make jenkins` ran
`./eng/install-dotnet.sh` because the file was committed as 100644.
The file from `make prepare` is invoked directly (not via `bash`), so
it needs the executable bit set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppersand others added 3 commits June 12, 2026 08:45
Reverts the executable-bit change from 2645bdb. Windows clones with
core.filemode=false would have shown spurious mode changes when editing
the file; running it via `bash ./eng/install-dotnet.sh` from the
Makefile sidesteps the bit entirely. Same trick for the cached
dotnet-install.sh we download under bin/.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This finishes the second half of the SDK provisioning audit started in
PR #11636. The first half moved the .NET SDK install into Microsoft's
official `dotnet-install.{sh,ps1}` scripts driven by `eng/install-dotnet`.
This commit replaces the leftover xaprepare logic that prepared
Android-specific .NET workloads against that SDK.
What `Step_PrepareDotNetWorkloads` did (now deleted):
* Restored `package-download.proj` to pull down the Mono Android runtime
packs and the Mono/Emscripten workload manifest packages.
* Copied the workload manifests from the NuGet package cache into the
local SDK's `sdk-manifests/` folder.
What `src/workloads/workloads.csproj` does (single MSBuild project, no
C#, no scenarios):
* Carries the same `<PackageDownload>` items that lived in
`package-download.proj` (run via NuGet's auto-restore).
* Has a `_CopyWorkloadManifests` target that runs `AfterTargets="Build"`
and copies each `microsoft.net.workload.{mono,emscripten}.<flavor>`
manifest's `data/` into the local SDK's
`sdk-manifests/<band>/microsoft.net.workload.<flavor>.<dotnet>/<ver>/`.
Per @jonathanpeppers' suggestion in
#11636 (comment 3403797084):
"move it to like `src/workloads/workloads.csproj` and that project is
built first."
Wiring:
* `Makefile prepare:` now runs
`dotnet build src/workloads/workloads.csproj` after the BootstrapTasks
build, before `PrepareJavaInterop`.
* `build-tools/scripts/PrepareWindows.targets`'s `Prepare` target adds
an `<MSBuild Projects=".../workloads.csproj" />` invocation in the
same spot.
* `build-tools/automation/yaml-templates/setup-test-environment-steps.yaml`
no longer invokes xaprepare. Test agents now run
`eng/install-dotnet.{sh,ps1}` (provisions the SDK at
`bin/$Config/dotnet/`) followed by
`dotnet build src/workloads/workloads.csproj` (provisions the
workloads against that SDK). This fixes the AndroidTestDependencies CI
failure introduced when the prior commit removed
`Step_InstallDotNetPreview`'s SDK download.
Cleanup:
* `Step_PrepareDotNetWorkloads.cs` and `package-download.proj` deleted.
* `Scenario_Standard` and `Scenario_AndroidTestDependencies` no longer
add `Step_PrepareDotNetWorkloads`.
* The `xaprepareScenario` parameter (and the now-unused
`run-xaprepare.yaml` template) are removed across all CI YAMLs.
* Dead `Configurables.MicrosoftNETWorkload*Dir` properties are removed.
Verified locally on Windows:
* `bin/Debug/dotnet/sdk-manifests/<band>/microsoft.net.workload.{mono.toolchain,emscripten}.{net6..net10,current}/<ver>/WorkloadManifest.json`
is populated after `dotnet build src/workloads/workloads.csproj` (12
manifests total).
* Re-running is idempotent (~0.5s warm; `Copy SkipUnchangedFiles="true"`).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppers
jonathanpeppers marked this pull request as ready for review June 17, 2026 20:04
CopilotAI review requested due to automatic review settings June 17, 2026 20:04

CopilotAI 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 migrates dotnet/android’s .NET SDK provisioning from xaprepare’s custom C# installer to the standard dotnet-install.{sh,ps1} flow, keeping the install location at bin/$Configuration/dotnet/ and moving Android-specific workload prep into a standalone MSBuild project.

Changes:

  • Add eng/install-dotnet.{sh,ps1} wrappers that read the pinned SDK version from eng/Versions.props, download dotnet-install.{sh,ps1}, and install into bin/$Configuration/dotnet.
  • Wire the new install/workload-prep flow into Makefile, Windows PrepareWindows.targets, and CI templates; remove xaprepare’s SDK-install step and related plumbing.
  • Introduce src/workloads/workloads.csproj to restore required runtime packs + workload manifest packages and copy manifests into the locally installed SDK.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/Xamarin.Installer.Build.Tasks/README.mdUpdates developer instructions to use eng/install-dotnet.* + build workloads project.
src/workloads/workloads.csprojNew MSBuild project to restore runtime packs/manifests and copy manifests into the local SDK.
MakefileAdds install-dotnet prerequisite and runs workloads provisioning during prepare.
eng/install-dotnet.shNew Unix bootstrap script to install pinned SDK into bin/$Configuration/dotnet.
eng/install-dotnet.ps1New Windows bootstrap script to install pinned SDK into bin\$Configuration\dotnet.
build-tools/xaprepare/xaprepare/Steps/Step_InstallDotNetPreview.csDeletes the bespoke xaprepare SDK installer step.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.csRemoves SDK install step from the standard xaprepare scenario.
build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.csRemoves SDK install step from Android test dependency scenario.
build-tools/xaprepare/xaprepare/package-download.projDeletes the old runtime-pack restore project used by xaprepare.
build-tools/xaprepare/xaprepare/Main.csRemoves --dotnet-sdk-archive option plumbing.
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Unix.csRemoves DotNet install script URL configurable (no longer needed).
build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.csRemoves workload manifest path helpers tied to the deleted step.
build-tools/xaprepare/xaprepare/Application/Context.csRemoves LocalDotNetSdkArchive property.
build-tools/scripts/PrepareWindows.targetsEnsures SDK install runs before building xaprepare; adds workloads provisioning to Prepare.
build-tools/automation/yaml-templates/stage-package-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/yaml-templates/setup-test-environment.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/setup-test-environment-steps.yamlReplaces xaprepare invocation with eng/install-dotnet.* + workloads provisioning.
build-tools/automation/yaml-templates/setup-test-environment-public.yamlRemoves xaprepareScenario parameter pass-through.
build-tools/automation/yaml-templates/run-xaprepare.yamlDeletes the shared pipeline template that ran xaprepare.
build-tools/automation/yaml-templates/run-emulator-tests.yamlRemoves xaprepare scenario parameter usage from test setup.
build-tools/automation/azure-pipelines-public.yamlRemoves xaprepare scenario parameter usage from test environment setup.
build-tools/automation/azure-pipelines-nightly.yamlRemoves xaprepare scenario parameter usage from test environment setup.

Comment threadMakefile Outdated
Comment threadeng/install-dotnet.ps1
jonathanpeppersand others added 2 commits June 17, 2026 15:43
* Makefile install-dotnet: pass CONFIGURATION through to install-dotnet.sh
so 'make CONFIGURATION=Release prepare' installs the SDK under
bin/Release/dotnet to match the rest of the build.
* eng/install-dotnet.ps1: null-check the result of SelectSingleNode before
dereferencing .InnerText so the script fails with the intended friendly
error message if <MicrosoftNETSdkPackageVersion> is ever removed from
eng/Versions.props.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous commit accidentally captured local submodule pointer changes
for external/Java.Interop and external/xamarin-android-tools that have
nothing to do with the SDK provisioning audit. Restore them to the
pointers used by the rest of this PR (and main).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jonathanpeppersjonathanpeppers added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Jun 22, 2026
@jonathanpeppers

Copy link
Copy Markdown
MemberAuthor

/review

@github-actions

github-actionsBot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

@github-actionsgithub-actionsBot 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.

🤖 Android PR Review — [build] Provision .NET SDK via standard scripts, drop xaprepare's installer

I reviewed the diff independently before reading the description. This is a well-scoped, largely subtractive change: the bespoke ~220-line Step_InstallDotNetPreview + package-download.proj are replaced by thin eng/install-dotnet.{sh,ps1} bootstrappers and a src/workloads/workloads.csproj (Microsoft.Build.NoTargets) that PackageDownloads the runtime packs / workload manifests and copies the manifests into the locally-installed SDK. Good direction — collapsing toward dotnet-install + dotnet build is a real maintainability win.

Verified OK (potential concerns I checked and dismissed)

  • ✅ No dangling references to the removed symbols (Step_InstallDotNetPreview, DotNetInstallScript, the MicrosoftNETWorkloadMono*Dir configurables, package-download.proj).
  • ✅ Every xaprepareScenario / run-xaprepare.yaml consumer was removed — no orphaned YAML parameters that would break pipeline parsing.
  • ✅ No provisioning silently dropped: androidsdk.csproj (SDK/JDK) and emulator setup are untouched; the affected scenarios were effectively no-ops apart from the removed step.
  • ✅ Property/import ordering in workloads.csproj is fine — DotNetStableTargetFramework, MicrosoftNETCoreAppRefPackageVersion, and the manifest bands resolve via Directory.Build.propseng/Versions.props (auto-imported before the body); XAPackagesDir / DotNetPreviewPath exist by the time the target runs. Microsoft.Build.NoTargets is pinned in global.json.
  • ✅ Backslash path separators in the copy target normalize correctly on Linux/macOS (verified empirically).
  • Makefile passes -p:Configuration=$(CONFIGURATION) to prepare-workloads, matching the install-dotnet install path (bin/$Configuration/dotnet).

Findings (none merge-blocking)

SevAreaNote
⚠️install scriptsA failed/partial download poisons the cached dotnet-install.{sh,ps1} — no temp-then-move, and an empty cached script silently "succeeds".
⚠️workloadsDrops the old forced stale-cache cleanup before copy; possible stale runtime packs/manifests if an internal version string is reused.
💡workloads target_CopyWorkloadManifests has no Inputs/Outputs and uses AfterTargets="Build".
💡formatting<Error>Condition should come first (Postmortem #33).

Notes

  • 📝 The PR description is slightly stale — it refers to a Step_PrepareDotNetWorkloads.cs replacement, but the actual change introduces src/workloads/workloads.csproj (and deletes package-download.proj). Worth updating so reviewers/git log archaeologists aren't misled.
  • CI for the head commit (11dc706) is still in progress (combined status pending; the dotnet-android build is queued/running). Please confirm it goes green before merging — an earlier commit's legs passed, but the current head hasn't completed.

Nice cleanup overall. 👍

Generated by Android PR Reviewer for issue #11636 · 1.7K AIC · ⌖ 66.1 AIC · ⊞ 37.8K
Comment /review to run again

Comment threadeng/install-dotnet.sh Outdated
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
Comment threadsrc/workloads/workloads.csproj
jonathanpeppersand others added 2 commits June 22, 2026 16:11
* eng/install-dotnet.{sh,ps1}: download Microsoft's dotnet-install
script to a temp file and atomically rename into place so a failed
or interrupted download cannot poison the cached script. Restores
the temp-then-move pattern the old Step_InstallDotNetPreview used.
* src/workloads/workloads.csproj: put Condition attribute first on the
<Error> task per repo convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These keep slipping into commits because the local worktree has stale
submodule pointers. Restore them to the PR's prior commit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
simonrozsival enabled auto-merge (squash) June 23, 2026 10:26
@jonathanpeppers
jonathanpeppers merged commit c0f2623 into mainJun 24, 2026
38 of 40 checks passed
@jonathanpeppers
jonathanpeppers deleted the jonathanpeppers/sdk-provisioning-audit branch June 24, 2026 18:44
simonrozsival pushed a commit that referenced this pull request Jun 25, 2026
After PR #11636 hollowed out `Scenario_AndroidTestDependencies` and
`Scenario_EmulatorTestDependencies`, their `AddSteps()` methods no
longer add any steps -- they only set `AllowProgramInstallation=false`
and `IgnoreMissingPrograms=true`, which have no effect when no steps
run. `Scenario_EmulatorTestDependencies` inherited from the former and
added nothing.
Delete both vestigial scenarios. Also update the now-obsolete error
message in `GradleCLI.cs` that referenced the deleted scenario; Gradle
is committed to the repo at `build-tools/gradle/`, so the generic
"not found" wording is sufficient.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
simonrozsival pushed a commit that referenced this pull request Jun 26, 2026
Many xaprepare provisioning steps have been removed over the past year (#11332, #11348, #11399, #11440, #11441, #11636 and follow-up cleanups in #11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737). The supporting scaffolding around those steps was left behind. This PR removes the verified-dead pieces in two passes.
## Files removed (first pass — original audit)
| File | Justification |
| --- | --- |
| `Application/TestAssembly.cs` | Orphan test infra; only referenced by `TestAssemblyType.cs`. |
| `Application/TestAssemblyType.cs` | Only referenced by `TestAssembly.cs`. |
| `Application/StepWithDownloadProgress.cs` | No subclasses remain. |
| `Application/NDKTool.cs` | NDK provisioning moved to MSBuild in #11440. Last consumer was the also-dead `Configurables.NDKTools` collection (removed below). |
| `ToolRunners/SnRunner.cs` | Strong-naming tool runner; never instantiated. |
| `ToolRunners/SnRunner.OutputSink.cs` | Partial sibling of `SnRunner`. |
| `ToolRunners/CMakeRunner.cs` | Never instantiated. |
| `ToolRunners/CMakeRunner.OutputSink.cs` | Partial sibling of `CMakeRunner`. |
## Files removed (second pass — repo-wide re-audit)
| File | Justification |
| --- | --- |
| `ToolRunners/MakeRunner.Linux.cs` | Partial of `MakeRunner`; type never instantiated. |
| `ToolRunners/MakeRunner.MacOS.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.OutputSink.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MakeRunner.Unix.cs` | Partial of `MakeRunner`. |
| `ToolRunners/MSBuildRunner.cs` | Never instantiated. |
| `ToolRunners/MSBuildRunner.OutputSink.cs` | Partial sibling of `MSBuildRunner`. |
| `ToolRunners/NinjaRunner.cs` | Never instantiated. |
| `ToolRunners/NinjaRunner.OutputSink.cs` | Partial sibling of `NinjaRunner`. |
| `Application/ScenarioNoStandardEndSteps.cs` | Abstract class with zero subclasses. |
## Cascading cleanup
- `ConfigAndData/Configurables.cs` — removed the dead `NDKTools` `List<NDKTool>` collection (lines 132–145). Rest of the file unchanged.
## Removed from initial deletion list after verification
- `Application/Extensions.DictionaryOfProgramVersionParser.cs` — initial name-only audit flagged it as dead, but its `Add` extension method is consumed via dictionary collection-initializer syntax in `Application/VersionFetchers.cs`. The consumer never references the static class by name, which is why the first audit missed it. The file stays.
- `Scenarios/Scenario_Required.cs` — looks unreferenced by static grep, but `Scenario` subclasses are reflectively discovered via the `[Scenario]` attribute in `Context.cs` (`Utilities.GetTypesWithCustomAttribute<ScenarioAttribute> ()`). Live. The file stays.
## Verification
- `git grep -n -w <TypeName>` for each deleted type now returns 0 real hits (only unrelated `"TestAssembly"` string literals in `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` remain — those are assembly-name strings, not the C# type).
- `dotnet build build-tools/xaprepare/xaprepare/xaprepare.csproj -c Debug` → 0 warnings, 0 errors.
## Deferred follow-up
The csproj conditionally excludes `*MacOS*` files from compilation when `HostOS != Darwin`, so static dead-code analysis from a Windows/Linux host can't see whether the macOS-only consumers are themselves live. These candidates need verification on a Mac host (or a build matrix) before deletion:
- `Application/PkgProgram.MacOS.cs`
- `Application/HomebrewProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
- `ConfigAndData/Dependencies/MacOS.cs`
simonrozsival pushed a commit that referenced this pull request Jun 30, 2026
### Context
After #11636 (dotnet provisioning step removed) and #11731 (test-deps scenarios removed), `Context.AutoProvision` is `false` by default everywhere except hand-run dev provisioning, which is no longer in use. The per-OS package lists are populated at `OS.Init()` time but `EnsureDependencies` is effectively a no-op:
- `OS.EnsureDependencies()` returns early when `AutoProvision` is false (the default),
- nothing else in the codebase reads from the `Program` derivatives' install/uninstall paths,
- the `BuildToolsInventory` writer remains driven only from `EssentialTools.MacOS.cs` (homebrew version detection).
The `OS.Init() / InitializeDependencies() / EnsureDependencies()` machinery on `OS.cs` itself is intentionally **left in place** here — that's a larger refactor for a follow-up PR. This PR only strips the now-vestigial package-list data and the program/runner classes that fed it.
### Files deleted (Phase F — macOS, 4 files)
- `Application/HomebrewProgram.MacOS.cs`
- `Application/PkgProgram.MacOS.cs`
- `ToolRunners/BrewRunner.MacOS.cs`
- `ToolRunners/PkgutilRunner.MacOS.cs`
### Files deleted (Phase G — Linux, 5 files)
- `Application/Program.Linux.cs` (`LinuxProgram` base — orphan after subclasses go)
- `Application/Program.ArchLinux.cs`
- `Application/Program.DebianLinux.cs`
- `Application/Program.FedoraLinux.cs`
- `Application/Program.GentooLinux.cs`
### Files deleted (Phase 3 — orphan)
- `Application/IBuildInventoryItem.cs` (only implementor was `HomebrewProgram`; `BuildToolsInventory` itself stays, populated directly by `EssentialTools.MacOS.cs`).
### Files reduced to empty stubs
`ConfigAndData/Dependencies/`:
- `MacOS.cs` — `InitializeDependencies()` no-op (was Homebrew formula list + git fallback).
- `Linux.Arch.cs` — class kept (referenced by `distroMap`); package list removed.
- `Linux.Fedora.cs` — same.
- `Linux.Gentoo.cs` — same.
- `Linux.DebianCommon.cs` — common Debian/Ubuntu package list removed; `Flavor = "Debian"` kept.
- `Linux.UbuntuCommon.cs` — `libtoolPackages` + `NeedLibtool` virtual + `InitOS` override removed (all dead).
- `Linux.Debian.cs` — all per-version package lists (`packages`, `packagesPre10`, `packagesPreTrixie`, `packagesTrixieAndLater`, `packages10AndNewerBuildBots`) removed; release/codename detection (`EnsureVersionInformation`, `DebianUnstableVersionMap`, `IsDebian10OrNewer`, etc.) preserved as conservative scope.
- `Linux.Ubuntu.cs` — `preCosmicPackages`, `cosmicPackages`, `preDiscoPackages` lists + `NeedLibtool` override removed; `UbuntuRelease` + `EnsureVersionInformation` preserved.
- `Linux.Mint.cs` — `NeedLibtool` override removed (the property is gone from the base).
`ConfigAndData/Dependencies/Windows.cs` was already a no-op stub — no edit.
### Verification
Orphan audit (each `git grep -nw <Type> -- 'build-tools/xaprepare/*'` reports **0 hits**):
- `HomebrewProgram`, `PkgProgram`, `BrewRunner`, `PkgutilRunner`
- `ArchLinuxProgram`, `DebianLinuxProgram`, `FedoraLinuxProgram`, `GentooLinuxProgram`, `LinuxProgram`
- `IBuildInventoryItem`
Build:
```
dotnet build build-tools\xaprepare\xaprepare\xaprepare.csproj -c Debug
Build succeeded. 0 Warning(s) 0 Error(s)
```
### Out of scope (follow-up)
- Removing the abstract `OS.InitializeDependencies()` declaration and the surrounding `EnsureDependencies()` machinery from `OperatingSystems/OS.cs`.
- `VersionFetchers` / `ProgramVersionParser` / `RegexProgramVersionParser` / `SevenZipVersionParser` / `Extensions.DictionaryOfProgramVersionParser.cs` are kept — `Utilities.GetProgramVersion` still queries them from `Program.cs`, `ToolRunner.cs`, `EssentialTools.MacOS.cs`, and `OperatingSystems/MacOS.cs` (brew detection).
### Precedent
#11568, #11580, #11608, #11613, #11631, #11657, #11658, #11731, #11732, #11733, #11737, #11740, #11760
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ready-to-reviewThis 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.

3 participants

@jonathanpeppers@simonrozsival