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)