diff --git a/Configuration.props b/Configuration.props
index c6f829d80d2..06fcb18457c 100644
--- a/Configuration.props
+++ b/Configuration.props
@@ -150,6 +150,8 @@
$(NUGET_PACKAGES)
$(userprofile)\.nuget\packages
$(HOME)/.nuget/packages
+ <_DotNetWorkloadEnvironment Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">DOTNET_CLI_HOME=$(_DotNetWorkloadUserHome);DOTNETSDK_WORKLOAD_MANIFEST_ROOTS=$(_DotNetWorkloadManifestRoot);DOTNETSDK_WORKLOAD_PACK_ROOTS=$(_DotNetWorkloadPackRoot)
+ <_DotNetBuildEnvironment Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">$(_DotNetWorkloadEnvironment);NUGET_PACKAGES=$([MSBuild]::EnsureTrailingSlash('$(XAPackagesDir)'))
$([System.IO.Path]::PathSeparator)
<_TestsAotName Condition=" '$(AotAssemblies)' == 'true' ">-Aot
<_TestsProfiledAotName Condition=" '$(AndroidEnableProfiledAot)' == 'true' ">-Profiled
diff --git a/Directory.Build.props b/Directory.Build.props
index baf6a832968..0b98012337c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,9 +16,25 @@
$(MSBuildThisFileDirectory)bin\Test$(Configuration)\
$(BootstrapOutputDirectory)$(DotNetStableTargetFramework)\Xamarin.Android.Tools.BootstrapTasks.dll
$(BootstrapOutputDirectory)$(DotNetStableTargetFramework)\xa-prep-tasks.dll
+ <_DotNetInstallLocationFile>$(BuildOutputDirectory)dotnet-install-location.txt
+ <_DotNetInstallLocation Condition=" '$(TF_BUILD)' == '' and '$(GITHUB_ACTIONS)' == '' and '$(CI)' == '' and Exists('$(_DotNetInstallLocationFile)') ">$([System.IO.File]::ReadAllText('$(_DotNetInstallLocationFile)').Trim())
+ <_DotNetSharedInstallBase Condition=" '$(XA_DOTNET_SHARED_INSTALL_BASE)' != '' and '$(TF_BUILD)' == '' and '$(GITHUB_ACTIONS)' == '' and '$(CI)' == '' ">$([System.IO.Path]::Combine('$(MSBuildThisFileDirectory)', '$(XA_DOTNET_SHARED_INSTALL_BASE)'))
+ <_DotNetSharedInstallPath Condition=" '$(_DotNetInstallLocation)' != '' and Exists('$(_DotNetInstallLocation)sdk\$(MicrosoftNETSdkPackageVersion)') ">$(_DotNetInstallLocation)
+ <_DotNetSharedInstallPath Condition=" '$(_DotNetSharedInstallPath)' == '' and '$(_DotNetSharedInstallBase)' != '' ">$(_DotNetSharedInstallBase)\$(MicrosoftNETSdkPackageVersion)
+ <_DotNetUsingSharedSdk Condition=" '$(_DotNetSharedInstallPath)' != '' ">true
+ $([MSBuild]::EnsureTrailingSlash('$(_DotNetSharedInstallPath)'))
$(BuildOutputDirectory)dotnet\
- $(DotNetPreviewPath)dotnet
+ $(DotNetPreviewPath)dotnet
dotnet
+ <_DotNetWorkloadPackRoot Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">$(BuildOutputDirectory)lib\
+ <_DotNetWorkloadPackRoot Condition=" '$(_DotNetWorkloadPackRoot)' == '' ">$(DotNetPreviewPath)
+ <_DotNetWorkloadManifestRoot Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">$(_DotNetWorkloadPackRoot)sdk-manifests\
+ <_DotNetWorkloadManifestRoot Condition=" '$(_DotNetWorkloadManifestRoot)' == '' ">$(DotNetPreviewPath)sdk-manifests\
+ <_DotNetWorkloadUserHome Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">$(BuildOutputDirectory)dotnet-home\
+ <_DotNetWorkloadUserProfileRoot Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">$(_DotNetWorkloadUserHome).dotnet\
+ <_DotNetWorkloadUserProfileRoot Condition=" '$(_DotNetWorkloadUserProfileRoot)' == '' ">$(DotNetPreviewPath)
+ <_DotNetWorkloadUserManifestRoot>$(_DotNetWorkloadUserProfileRoot)sdk-manifests\
+ <_DotNetWorkloadUserPackRoot>$(_DotNetWorkloadUserProfileRoot)packs\
true
diff --git a/Documentation/building/unix/instructions.md b/Documentation/building/unix/instructions.md
index 24deeb20938..21e0968c309 100644
--- a/Documentation/building/unix/instructions.md
+++ b/Documentation/building/unix/instructions.md
@@ -58,6 +58,21 @@ on Windows, many of the concepts should still apply:
`make prepare` provisions a specific build of .NET to
`bin/$(Configuration)/dotnet`.
+To share the provisioned SDK between multiple worktrees, set
+`XA_DOTNET_SHARED_INSTALL_BASE` to a common base directory before running
+`make prepare`:
+
+ $ export XA_DOTNET_SHARED_INSTALL_BASE="$HOME/android-dotnet-sdk"
+ $ make prepare
+
+The SDK will be installed under
+`$XA_DOTNET_SHARED_INSTALL_BASE/`, shared by Debug and Release
+builds. The shared SDK is treated as read-only after provisioning; workload
+packs, manifests, and installation state remain under `bin/$(Configuration)`.
+The override is ignored when `TF_BUILD`, `GITHUB_ACTIONS`, or `CI` is set, so
+CI builds and local builds without `XA_DOTNET_SHARED_INSTALL_BASE` continue to
+use `bin/$(Configuration)/dotnet`.
+
Once `make all` or `make jenkins` have completed, your local
`bin/$(Configuration)/lib/packs` directory will be populated with a
local Android "workload" in `Microsoft.Android.Sdk.$(HostOS)` matching
@@ -78,8 +93,8 @@ Build the project with:
$ ./dotnet-local.sh build foo.csproj
-Using the `dotnet-local` script will execute the `dotnet` provisioned in
-`bin/$(Configuration)/dotnet` and will use the locally built binaries.
+Using the `dotnet-local` script will execute the provisioned `dotnet` and
+will use the locally built binaries.
See the [One .NET Documentation](../../guides/OneDotNet.md) for further details.
diff --git a/Documentation/building/windows/instructions.md b/Documentation/building/windows/instructions.md
index b6ece0df558..cd6723e706e 100644
--- a/Documentation/building/windows/instructions.md
+++ b/Documentation/building/windows/instructions.md
@@ -91,8 +91,23 @@ Or in powershell:
> dotnet-local.cmd build foo.csproj
-Using the `dotnet-local` script will execute the `dotnet` provisioned in
-`bin\$(Configuration)\dotnet` and will use the locally built binaries.
+Using the `dotnet-local` script will execute the provisioned `dotnet` and
+will use the locally built binaries.
+
+To share the provisioned SDK between multiple worktrees, set
+`XA_DOTNET_SHARED_INSTALL_BASE` to a common base directory before running the
+`Prepare` target:
+
+ $env:XA_DOTNET_SHARED_INSTALL_BASE = Join-Path $HOME 'android-dotnet-sdk'
+ dotnet msbuild Xamarin.Android.slnx -t:Prepare
+
+The SDK will be installed under
+`$env:XA_DOTNET_SHARED_INSTALL_BASE\`, shared by Debug and Release
+builds. The shared SDK is treated as read-only after provisioning; workload
+packs, manifests, and installation state remain under `bin\$(Configuration)`.
+The override is ignored when `TF_BUILD`, `GITHUB_ACTIONS`, or `CI` is set, so
+CI builds and local builds without `XA_DOTNET_SHARED_INSTALL_BASE` continue to
+use `bin\$(Configuration)\dotnet`.
See the [One .NET Documentation](../../guides/OneDotNet.md) for further details.
diff --git a/build-tools/create-packs/ConfigureLocalWorkload.targets b/build-tools/create-packs/ConfigureLocalWorkload.targets
index 1cb543b424b..81ee12867ea 100644
--- a/build-tools/create-packs/ConfigureLocalWorkload.targets
+++ b/build-tools/create-packs/ConfigureLocalWorkload.targets
@@ -95,6 +95,8 @@
<_LocalSdkManifestsFolder>$(BuildOutputDirectory)lib\sdk-manifests\$(_LocalSdkFeatureBand)\
<_LocalAndroidManifestFolder>$(_LocalSdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\
<_EmptyWorkloadDir>$(_LocalSdkManifestsFolder)android.deps.workload\0.0.1\
+ <_InstallManifestEnvironment Condition=" '$(_DotNetUsingSharedSdk)' == 'true' ">$(_DotNetBuildEnvironment)
+ <_InstallManifestEnvironment Condition=" '$(_InstallManifestEnvironment)' == '' ">DOTNETSDK_WORKLOAD_MANIFEST_ROOTS=$(BuildOutputDirectory)lib\sdk-manifests
@@ -128,7 +130,7 @@
/>
diff --git a/build-tools/create-packs/Directory.Build.targets b/build-tools/create-packs/Directory.Build.targets
index f69919ef4b2..53f99c2649b 100644
--- a/build-tools/create-packs/Directory.Build.targets
+++ b/build-tools/create-packs/Directory.Build.targets
@@ -88,7 +88,7 @@
<_WLManifest Include="$(XamarinAndroidSourcePath)bin\Build$(Configuration)\nuget-unsigned\Microsoft.NET.Sdk.Android.Manifest-*.nupkg" />
- <_SdkManifestsFolder>$(DotNetPreviewPath)sdk-manifests\$(DotNetSdkManifestsFolder)\
+ <_SdkManifestsFolder>$(_DotNetWorkloadUserManifestRoot)$(DotNetSdkManifestsFolder)\
<_InstallArguments Include="--temp-dir "$(_TempDirectory)"" />
-
+
@@ -151,18 +152,18 @@
<_PackApiLevels Include="$(AndroidLatestUnstableApiLevel)" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)metadata" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)sdk-manifests\$(DotNetSdkManifestsFolder)\workloadsets" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)sdk-manifests\$(DotNetSdkManifestsFolder)\microsoft.net.sdk.android" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Ref.%(_PackApiLevels.Identity)" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-arm" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-arm64" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-x86" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-x64" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Sdk.Darwin" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Sdk.Linux" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)packs\Microsoft.Android.Sdk.Windows" />
- <_PackFoldersToDelete Include="$(DotNetPreviewPath)template-packs" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserProfileRoot)metadata" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserManifestRoot)$(DotNetSdkManifestsFolder)\workloadsets" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserManifestRoot)$(DotNetSdkManifestsFolder)\microsoft.net.sdk.android" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Ref.%(_PackApiLevels.Identity)" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-arm" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-arm64" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-x86" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Runtime.Mono.%(_PackApiLevels.Identity).android-x64" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Sdk.Darwin" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Sdk.Linux" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserPackRoot)Microsoft.Android.Sdk.Windows" />
+ <_PackFoldersToDelete Include="$(_DotNetWorkloadUserProfileRoot)template-packs" />
diff --git a/build-tools/scripts/DotNet.targets b/build-tools/scripts/DotNet.targets
index 6be7b0637be..07c0d814a2f 100644
--- a/build-tools/scripts/DotNet.targets
+++ b/build-tools/scripts/DotNet.targets
@@ -17,6 +17,7 @@
@@ -25,6 +26,7 @@
DependsOnTargets="PrepareOpenJDK">
@@ -92,7 +94,7 @@
- <_TempDirectory>$(DotNetPreviewPath)..\.xa-workload-temp-$([System.IO.Path]::GetRandomFileName())
+ <_TempDirectory>$(BuildOutputDirectory)obj\.xa-workload-temp-$([System.IO.Path]::GetRandomFileName())
$(DotNetSdkManifestsFolder)
@@ -102,7 +104,7 @@
Condition=" '$(MauiUseLocalPacks)' != 'true' "
Command=""$(DotNetPreviewTool)" restore maui.proj -p:MauiVersion=$(MauiVersion) -p:MauiVersionBand=$(MauiVersionBand)"
WorkingDirectory="$(MSBuildThisFileDirectory)"
- EnvironmentVariables="NUGET_PACKAGES=$(_TempDirectory);DOTNET_MULTILEVEL_LOOKUP=0"
+ EnvironmentVariables="NUGET_PACKAGES=$(_TempDirectory);DOTNET_MULTILEVEL_LOOKUP=0;$(_DotNetWorkloadEnvironment)"
/>
<_WLManifestPack Include="$(MauiPackagePath)\Microsoft.NET.Sdk.Maui.Manifest-$(MauiVersionBand.Substring (0,3))*.nupkg" />
@@ -120,7 +122,7 @@
-
+
diff --git a/build-tools/scripts/Prepare.proj b/build-tools/scripts/Prepare.proj
index 8c51b180699..47e89706af4 100644
--- a/build-tools/scripts/Prepare.proj
+++ b/build-tools/scripts/Prepare.proj
@@ -22,10 +22,12 @@
diff --git a/build-tools/scripts/msbuild.mk b/build-tools/scripts/msbuild.mk
index bc9e989405f..3296adf4d6b 100644
--- a/build-tools/scripts/msbuild.mk
+++ b/build-tools/scripts/msbuild.mk
@@ -23,10 +23,26 @@
# $(MSBUILD_FLAGS): Additional MSBuild flags; contains $(CONFIGURATION), $(V), $(MSBUILD_ARGS).
MSBUILD = msbuild
-DOTNET_ROOT = $(topdir)/bin/$(CONFIGURATION)/dotnet/
+DOTNET_INSTALL_LOCATION = $(topdir)/bin/$(CONFIGURATION)/dotnet-install-location.txt
+DOTNET_SDK_VERSION = $(shell sed -n 's|.*\([^<]*\).*|\1|p' "$(topdir)/eng/Versions.props" | head -n 1)
+ifeq ($(strip $(TF_BUILD)$(GITHUB_ACTIONS)$(CI)),)
+_DOTNET_SHARED_ROOT = $(if $(wildcard $(DOTNET_INSTALL_LOCATION)),$(shell root=$$(cat "$(DOTNET_INSTALL_LOCATION)"); test -x "$${root}dotnet" && test -d "$${root}sdk/$(DOTNET_SDK_VERSION)" && printf '%s' "$$root"))
+endif
+DOTNET_ROOT = $(if $(_DOTNET_SHARED_ROOT),$(_DOTNET_SHARED_ROOT),$(topdir)/bin/$(CONFIGURATION)/dotnet/)
DOTNET_TOOL = $(DOTNET_ROOT)dotnet
DOTNET_VERB = build
MSBUILD_FLAGS = /p:Configuration=$(CONFIGURATION) $(MSBUILD_ARGS)
+ifneq ($(_DOTNET_SHARED_ROOT),)
+DOTNET_CLI_HOME = $(topdir)/bin/$(CONFIGURATION)/dotnet-home
+DOTNETSDK_WORKLOAD_MANIFEST_ROOTS = $(topdir)/bin/$(CONFIGURATION)/lib/sdk-manifests
+DOTNETSDK_WORKLOAD_PACK_ROOTS = $(topdir)/bin/$(CONFIGURATION)/lib
+NUGET_PACKAGES := $(patsubst %/,%,$(if $(NUGET_PACKAGES),$(NUGET_PACKAGES),$(HOME)/.nuget/packages))/
+
+export DOTNET_CLI_HOME
+export DOTNETSDK_WORKLOAD_MANIFEST_ROOTS
+export DOTNETSDK_WORKLOAD_PACK_ROOTS
+export NUGET_PACKAGES
+endif
ifeq ($(OS_NAME),Darwin)
_PKG_CONFIG = /Library/Frameworks/Mono.framework/Commands/pkg-config
@@ -52,7 +68,7 @@ endef
# $(call DOTNET_BINLOG,name,build=$(DOTNET_VERB),dotnet=$(DOTNET_TOOL))
define DOTNET_BINLOG
- $(if $(3),,PATH="$(DOTNET_ROOT):$(PATH)") $(if $(3),$(3),$(DOTNET_TOOL)) $(if $(2),$(2),$(DOTNET_VERB)) -p:Configuration=$(CONFIGURATION) -v:n $(MSBUILD_ARGS) \
+ $(if $(3),,PATH="$(DOTNET_ROOT):$(PATH)") $(if $(3),$(3),"$(DOTNET_TOOL)") $(if $(2),$(2),$(DOTNET_VERB)) -p:Configuration=$(CONFIGURATION) -v:n $(MSBUILD_ARGS) \
-bl:"$(dir $(realpath $(firstword $(MAKEFILE_LIST))))/bin/Build$(CONFIGURATION)/msbuild-`date +%Y%m%dT%H%M%S`-$(1).binlog"
endef
diff --git a/dotnet-local.cmd b/dotnet-local.cmd
index 448303606bb..306008f2862 100644
--- a/dotnet-local.cmd
+++ b/dotnet-local.cmd
@@ -2,21 +2,50 @@
SETLOCAL
SET ROOT=%~dp0
+SET XA_CONFIG=
+SET XA_DOTNET_ROOT=
+SET XA_DOTNET_SDK_VERSION=
+SET XA_USING_SHARED_DOTNET=
+SET XA_RUNNING_ON_CI=
-IF EXIST "%ROOT%\bin\Release\dotnet\dotnet.exe" (
- SET XA_CONFIG=Release
-) ELSE IF EXIST "%ROOT%\bin\Debug\dotnet\dotnet.exe" (
- SET XA_CONFIG=Debug
-) ELSE (
- echo "You need to run 'msbuild Xamarin.Android.slnx /t:Prepare' first."
- goto :exit
-)
+IF DEFINED TF_BUILD SET XA_RUNNING_ON_CI=true
+IF DEFINED GITHUB_ACTIONS SET XA_RUNNING_ON_CI=true
+IF DEFINED CI SET XA_RUNNING_ON_CI=true
-SET XA_DOTNET_ROOT=%ROOT%\bin\%XA_CONFIG%\dotnet
+FOR /F "tokens=2 delims=<>" %%V IN ('findstr /C:"" "%ROOT%\eng\Versions.props"') DO SET XA_DOTNET_SDK_VERSION=%%V
+
+CALL :find_dotnet Release
+IF DEFINED XA_CONFIG GOTO :dotnet_found
+CALL :find_dotnet Debug
+IF DEFINED XA_CONFIG GOTO :dotnet_found
+
+echo "You need to run 'msbuild Xamarin.Android.slnx /t:Prepare' first."
+GOTO :exit
+
+:dotnet_found
SET PATH=%XA_DOTNET_ROOT%;%PATH%
+IF DEFINED XA_USING_SHARED_DOTNET CALL :configure_shared_dotnet
SET DOTNETSDK_WORKLOAD_MANIFEST_ROOTS=%ROOT%\bin\%XA_CONFIG%\lib\sdk-manifests
SET DOTNETSDK_WORKLOAD_PACK_ROOTS=%ROOT%\bin\%XA_CONFIG%\lib
call "%XA_DOTNET_ROOT%\dotnet.exe" %*
+GOTO :exit
+
+:find_dotnet
+SET XA_DOTNET_ROOT=
+IF NOT DEFINED XA_RUNNING_ON_CI IF EXIST "%ROOT%\bin\%1\dotnet-install-location.txt" SET /P XA_DOTNET_ROOT=<"%ROOT%\bin\%1\dotnet-install-location.txt"
+IF EXIST "%XA_DOTNET_ROOT%\dotnet.exe" IF EXIST "%XA_DOTNET_ROOT%\sdk\%XA_DOTNET_SDK_VERSION%\" SET XA_USING_SHARED_DOTNET=true
+IF NOT DEFINED XA_USING_SHARED_DOTNET SET XA_DOTNET_ROOT=%ROOT%\bin\%1\dotnet\
+IF EXIST "%XA_DOTNET_ROOT%\dotnet.exe" SET XA_CONFIG=%1
+EXIT /B
+
+:configure_shared_dotnet
+IF NOT DEFINED NUGET_PACKAGES SET NUGET_PACKAGES=%USERPROFILE%\.nuget\packages
+IF "%NUGET_PACKAGES:~-1%"=="\" GOTO :shared_dotnet_configured
+IF "%NUGET_PACKAGES:~-1%"=="/" GOTO :shared_dotnet_configured
+SET NUGET_PACKAGES=%NUGET_PACKAGES%\
+:shared_dotnet_configured
+SET DOTNET_CLI_HOME=%ROOT%\bin\%XA_CONFIG%\dotnet-home
+EXIT /B
:exit
diff --git a/dotnet-local.sh b/dotnet-local.sh
index ca26fa1f75e..c36fed26c2b 100755
--- a/dotnet-local.sh
+++ b/dotnet-local.sh
@@ -1,15 +1,31 @@
#!/bin/bash
ROOT="$(dirname "${BASH_SOURCE}")"
FULLROOT="$(cd "${ROOT}"; pwd)"
+sdk_version="$(sed -n 's|.*\([^<]*\).*|\1|p' "${FULLROOT}/eng/Versions.props" | head -n 1)"
for config in Release Debug ; do
- if [[ ! -x "${ROOT}/bin/${config}/dotnet/dotnet" ]] ; then
+ XA_USING_SHARED_DOTNET=false
+ install_location="${FULLROOT}/bin/${config}/dotnet-install-location.txt"
+ if [[ -z "${TF_BUILD:-}" && -z "${GITHUB_ACTIONS:-}" && -z "${CI:-}" && -f "${install_location}" ]] ; then
+ IFS= read -r XA_DOTNET_ROOT < "${install_location}"
+ if [[ -x "${XA_DOTNET_ROOT}/dotnet" && -d "${XA_DOTNET_ROOT}/sdk/${sdk_version}" ]] ; then
+ XA_USING_SHARED_DOTNET=true
+ fi
+ fi
+ if [[ "$XA_USING_SHARED_DOTNET" != true ]] ; then
+ XA_DOTNET_ROOT="${FULLROOT}/bin/${config}/dotnet"
+ fi
+ if [[ ! -x "${XA_DOTNET_ROOT}/dotnet" ]] ; then
continue
fi
- XA_DOTNET_ROOT="${FULLROOT}/bin/${config}/dotnet"
export PATH="${XA_DOTNET_ROOT}:${PATH}"
+ if [[ "$XA_USING_SHARED_DOTNET" == true ]] ; then
+ NUGET_PACKAGES="${NUGET_PACKAGES:-${HOME}/.nuget/packages}"
+ export NUGET_PACKAGES="${NUGET_PACKAGES%/}/"
+ export DOTNET_CLI_HOME="${FULLROOT}/bin/${config}/dotnet-home"
+ fi
export DOTNETSDK_WORKLOAD_MANIFEST_ROOTS="${FULLROOT}/bin/${config}/lib/sdk-manifests"
export DOTNETSDK_WORKLOAD_PACK_ROOTS="${FULLROOT}/bin/${config}/lib"
- exec "${ROOT}/bin/${config}/dotnet/dotnet" "$@"
+ exec "${XA_DOTNET_ROOT}/dotnet" "$@"
done
echo "You need to run 'make prepare' first." >&2
diff --git a/eng/install-dotnet.ps1 b/eng/install-dotnet.ps1
index aa10f476cf9..3c46ed2b07a 100644
--- a/eng/install-dotnet.ps1
+++ b/eng/install-dotnet.ps1
@@ -1,11 +1,14 @@
<#
.SYNOPSIS
- Provisions the .NET SDK into bin\$Configuration\dotnet\.
+ Provisions the .NET SDK into bin\$Configuration\dotnet\ by default.
.DESCRIPTION
The SDK version is read from eng\Versions.props (single source of truth
kept up to date by darc when Microsoft.NET.Sdk flows from dotnet/dotnet),
so global.json does not need a 'tools.dotnet' pin.
+
+ Set XA_DOTNET_SHARED_INSTALL_BASE to a shared base directory to install the
+ SDK under \\.
#>
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -25,8 +28,24 @@ if ($null -eq $sdkNode -or [string]::IsNullOrWhiteSpace($sdkNode.InnerText)) {
exit 1
}
$sdkVersion = $sdkNode.InnerText
+$sdkBaseVersion = [Version]($sdkVersion.Split('-', 2)[0])
+$sdkFeatureBand = '{0}.{1}.{2}' -f $sdkBaseVersion.Major, $sdkBaseVersion.Minor, ([Math]::Floor($sdkBaseVersion.Build / 100) * 100)
+
+$useSharedInstall = -not [string]::IsNullOrWhiteSpace($env:XA_DOTNET_SHARED_INSTALL_BASE) -and
+ [string]::IsNullOrEmpty($env:TF_BUILD) -and
+ [string]::IsNullOrEmpty($env:GITHUB_ACTIONS) -and
+ [string]::IsNullOrEmpty($env:CI)
-$installDir = Join-Path $repoRoot "bin\$configuration\dotnet"
+if (-not $useSharedInstall) {
+ $installDir = Join-Path $repoRoot "bin\$configuration\dotnet"
+} else {
+ if ([IO.Path]::IsPathRooted($env:XA_DOTNET_SHARED_INSTALL_BASE)) {
+ $installBase = [IO.Path]::GetFullPath($env:XA_DOTNET_SHARED_INSTALL_BASE)
+ } else {
+ $installBase = [IO.Path]::GetFullPath((Join-Path $repoRoot $env:XA_DOTNET_SHARED_INSTALL_BASE))
+ }
+ $installDir = Join-Path $installBase $sdkVersion
+}
New-Item -ItemType Directory -Force -Path $installDir | Out-Null
# Download Microsoft's official dotnet-install.ps1 (cached under $installDir
@@ -52,3 +71,17 @@ Write-Host "Installing .NET SDK $sdkVersion into $installDir"
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
+
+$installLocationFile = Join-Path $repoRoot "bin\$configuration\dotnet-install-location.txt"
+if ($useSharedInstall) {
+ $userLocalMarker = Join-Path $installDir "metadata\workloads\$sdkFeatureBand\userlocal"
+ if (-not (Test-Path $userLocalMarker)) {
+ New-Item -ItemType Directory -Force -Path (Split-Path -Parent $userLocalMarker) | Out-Null
+ New-Item -ItemType File -Path $userLocalMarker | Out-Null
+ }
+ $installLocation = $installDir.TrimEnd([IO.Path]::DirectorySeparatorChar, [IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
+ New-Item -ItemType Directory -Force -Path (Split-Path -Parent $installLocationFile) | Out-Null
+ Set-Content -LiteralPath $installLocationFile -Value $installLocation -NoNewline
+} elseif (Test-Path $installLocationFile) {
+ Remove-Item -LiteralPath $installLocationFile -Force
+}
diff --git a/eng/install-dotnet.sh b/eng/install-dotnet.sh
index 9574bf1845a..674af8ee222 100644
--- a/eng/install-dotnet.sh
+++ b/eng/install-dotnet.sh
@@ -1,13 +1,15 @@
#!/usr/bin/env bash
#
-# Provisions the .NET SDK into bin/$Configuration/dotnet/.
+# Provisions the .NET SDK into bin/$Configuration/dotnet/ by default.
#
# The SDK version is read from eng/Versions.props (single source of truth
# kept up to date by darc when Microsoft.NET.Sdk flows from dotnet/dotnet),
# so global.json does not need a 'tools.dotnet' pin.
#
# Inputs (env vars):
-# CONFIGURATION - Debug (default) or Release; controls install path.
+# CONFIGURATION - Debug (default) or Release; controls checkout output.
+# XA_DOTNET_SHARED_INSTALL_BASE - Optional shared base directory. The SDK is
+# installed under //.
#
set -euo pipefail
@@ -24,7 +26,28 @@ if [[ -z "$sdk_version" ]]; then
exit 1
fi
-install_dir="$repo_root/bin/$configuration/dotnet"
+sdk_version_core="${sdk_version%%-*}"
+IFS=. read -r sdk_major sdk_minor sdk_patch _ <<< "$sdk_version_core"
+if [[ ! "$sdk_major" =~ ^[0-9]+$ || ! "$sdk_minor" =~ ^[0-9]+$ || ! "$sdk_patch" =~ ^[0-9]+$ ]]; then
+ echo "error: could not determine the SDK feature band from '$sdk_version'" >&2
+ exit 1
+fi
+sdk_feature_band="$sdk_major.$sdk_minor.$((10#$sdk_patch / 100 * 100))"
+
+use_shared_install=false
+if [[ -n "${XA_DOTNET_SHARED_INSTALL_BASE:-}" && -z "${TF_BUILD:-}" && -z "${GITHUB_ACTIONS:-}" && -z "${CI:-}" ]]; then
+ use_shared_install=true
+ if [[ "$XA_DOTNET_SHARED_INSTALL_BASE" = /* ]]; then
+ install_base="$XA_DOTNET_SHARED_INSTALL_BASE"
+ else
+ install_base="$repo_root/$XA_DOTNET_SHARED_INSTALL_BASE"
+ fi
+ mkdir -p "$install_base"
+ install_base="$(cd -P "$install_base" && pwd)"
+ install_dir="$install_base/$sdk_version"
+else
+ install_dir="$repo_root/bin/$configuration/dotnet"
+fi
mkdir -p "$install_dir"
# Download Microsoft's official dotnet-install.sh (cached under
@@ -41,3 +64,17 @@ fi
echo "Installing .NET SDK $sdk_version into $install_dir"
bash "$install_script" --version "$sdk_version" --install-dir "$install_dir" --no-path
+
+install_location_file="$repo_root/bin/$configuration/dotnet-install-location.txt"
+mkdir -p "$(dirname "$install_location_file")"
+if [[ "$use_shared_install" == true ]]; then
+ # Keep workload packs, manifests, and installation records outside the shared SDK.
+ userlocal_marker="$install_dir/metadata/workloads/$sdk_feature_band/userlocal"
+ if [[ ! -f "$userlocal_marker" ]]; then
+ mkdir -p "$(dirname "$userlocal_marker")"
+ : > "$userlocal_marker"
+ fi
+ printf '%s/\n' "${install_dir%/}" > "$install_location_file"
+else
+ rm -f "$install_location_file"
+fi
diff --git a/src/workloads/workloads.csproj b/src/workloads/workloads.csproj
index d35b82dc2dd..4909e4373c8 100644
--- a/src/workloads/workloads.csproj
+++ b/src/workloads/workloads.csproj
@@ -2,7 +2,7 @@
***********************************************************************************************
workloads.csproj
-Provisions the Android-specific .NET workloads against the .NET SDK that
+Provisions the Android-specific .NET workloads for the .NET SDK that
`eng/install-dotnet.{sh,ps1}` has already installed at
`bin/$(Configuration)/dotnet/`:
@@ -10,8 +10,8 @@ Provisions the Android-specific .NET workloads against the .NET SDK that
* Restores the Mono and Emscripten workload manifest packages
(Microsoft.NET.Workload.{Mono,Emscripten}.*.Manifest-*).
* Copies the workload manifests from the NuGet package cache into the
- local SDK's `sdk-manifests/` folder so the SDK can resolve the
- workloads against the locally installed runtime packs.
+ checkout-local workload root so the SDK can resolve the workloads
+ against the locally installed runtime packs.
Run via:
@@ -61,14 +61,14 @@ inside xaprepare.
<_DotNetSdk>$(DotNetPreviewPath.TrimEnd('\').TrimEnd('/'))
- <_SdkManifests>$(_DotNetSdk)\sdk-manifests
+ <_SdkManifests>$(_DotNetWorkloadManifestRoot)