Skip to content

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

Draft
jonathanpeppers wants to merge 4 commits into
mainfrom
jonathanpeppers/sdk-provisioning-audit
Draft

[build] Provision .NET SDK via standard scripts, drop xaprepare's installer#11636
jonathanpeppers wants to merge 4 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

Action Time
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)

Step Migration target
Step_PrepareDotNetWorkloads MSBuild .targets file
Step_GenerateCGManifest CI 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.

jonathanpeppers and 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>
protected override void AddSteps (Context context)
{
Steps.Add (new Step_InstallDotNetPreview ());
Steps.Add (new Step_PrepareDotNetWorkloads ());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm slightly confused by this new step. Will we turn this step into a script in a follow-up PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the leftover part of Step_InstallDotNetPreview that does workloads.

I'm wondering if we can move it to like src/workloads/workloads.csproj and that project is built first

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants