From fdf84cef5b6b14709b582a1f221e801cbefbffc6 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 18 May 2026 16:31:43 -0500 Subject: [PATCH 1/6] Move aapt2 download from xaprepare to MSBuild target Remove Step_Get_Android_BuildTools from xaprepare and move the aapt2 download/extract logic into src/aapt2/aapt2.targets as a proper MSBuild target. The new _DownloadBuildTools target: - Downloads Google's repository2-3.xml manifest - Dynamically reads SHA-1 checksums via XmlPeek (no hardcoded hashes) - Downloads the 3 platform build-tools zips - Validates checksums and errors on mismatch - Uses Inputs/Outputs for incremental build skip This removes one more step from xaprepare, moving the logic to a standard MSBuild target that is easier to understand and maintain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../xaprepare/Application/Context.cs | 5 - .../xaprepare/Scenarios/Scenario_Standard.cs | 1 - .../Steps/Step_Get_Android_BuildTools.cs | 72 -------------- src/aapt2/aapt2.targets | 96 ++++++++++++++++++- 4 files changed, 95 insertions(+), 79 deletions(-) delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs diff --git a/build-tools/xaprepare/xaprepare/Application/Context.cs b/build-tools/xaprepare/xaprepare/Application/Context.cs index 2b634fc9976..d88afc51e21 100644 --- a/build-tools/xaprepare/xaprepare/Application/Context.cs +++ b/build-tools/xaprepare/xaprepare/Application/Context.cs @@ -292,11 +292,6 @@ public string DebugFileExtension { /// public string? LocalDotNetSdkArchive { get; set; } - /// - /// Set by if the archive has been downloaded and validated. - /// - public bool BuildToolsArchiveDownloaded { get; set; } - /// /// Determines whether or not we are running on a hosted azure pipelines agent. /// These agents have certain limitations, the most pressing being the amount of available storage. diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs index ac32d24bd9b..200a43c9399 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs @@ -24,7 +24,6 @@ protected override void AddSteps (Context context) Steps.Add (new Step_GenerateFiles (atBuildStart: true)); Steps.Add (new Step_PrepareProps ()); Steps.Add (new Step_GenerateCGManifest ()); - Steps.Add (new Step_Get_Android_BuildTools ()); } protected override void AddEndSteps (Context context) diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs deleted file mode 100644 index 21078bcfd34..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Get_Android_BuildTools.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using System.Net; - -namespace Xamarin.Android.Prepare -{ - partial class Step_Get_Android_BuildTools : StepWithDownloadProgress - { - List<(string package, string prefix)> packages = new List<(string package, string prefix)>(); - - public Step_Get_Android_BuildTools () - : base ("Downloading build-tools archive") - { - string XABuildToolsVersion = Context.Instance.Properties [KnownProperties.XABuildToolsVersion] ?? String.Empty; - string XABuildToolsPackagePrefixMacOS = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefixMacOS] ?? string.Empty; - string XABuildToolsPackagePrefixWindows = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefixWindows] ?? string.Empty; - string XABuildToolsPackagePrefixLinux = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefixLinux] ?? string.Empty; - - packages.Add ((package: $"build-tools_r{XABuildToolsVersion}_macosx.zip", prefix: XABuildToolsPackagePrefixMacOS)); - packages.Add ((package: $"build-tools_r{XABuildToolsVersion}_windows.zip", prefix: XABuildToolsPackagePrefixWindows)); - packages.Add ((package: $"build-tools_r{XABuildToolsVersion}_linux.zip", prefix: XABuildToolsPackagePrefixLinux)); - } - - protected override async Task Execute (Context context) - { - bool success = true; - foreach (var package in packages) { - success &= await DownloadBuildToolsPackage (context, package.prefix + package.package, package.package); - if (!success) { - Log.InfoLine ($"build-tools package '{package.package}' not present"); - return false; - } - } - - context.BuildToolsArchiveDownloaded = success; - return true; - } - - async Task DownloadBuildToolsPackage (Context context, string packageName, string localPackageName) - { - string localArchivePath = Path.Combine (Configurables.Paths.AndroidBuildToolsCacheDir, localPackageName); - Uri url = new Uri (AndroidToolchain.AndroidUri, packageName); - - if (Utilities.FileExists (localArchivePath)) { - Log.StatusLine ($"build-tools already downloaded ({localArchivePath})"); - return true; - } - - Log.StatusLine ($"Downloading {packageName} from ", url.ToString (), tailColor: ConsoleColor.White); - (bool success, ulong size, HttpStatusCode status) = await Utilities.GetDownloadSizeWithStatus (url); - if (!success) { - if (status == HttpStatusCode.NotFound) - Log.ErrorLine ("build-tools URL not found"); - else - Log.ErrorLine ("Failed to obtain build-tools size. HTTP status code: {status} ({(int)status})"); - return false; - } - DownloadStatus downloadStatus = Utilities.SetupDownloadStatus (context, size, context.InteractiveSession); - Log.StatusLine ($" {context.Characters.Link} {url}", ConsoleColor.White); - await Download (context, url, localArchivePath, "build-tools", Path.GetFileName (localArchivePath), downloadStatus); - - if (!File.Exists (localArchivePath)) { - Log.ErrorLine ($"Download of build-tools from {url} failed"); - return false; - } - - return true; - } - } -} diff --git a/src/aapt2/aapt2.targets b/src/aapt2/aapt2.targets index 3a1f5c1e7a5..a0f30bf8da3 100644 --- a/src/aapt2/aapt2.targets +++ b/src/aapt2/aapt2.targets @@ -3,29 +3,123 @@ 4.0.0-6051327 <_Destination>$(MicrosoftAndroidSdkOutDir) obj\$(Configuration)\ + <_AndroidManifestPath>$(AndroidToolchainCacheDirectory)\repository2-3.xml + <_AndroidRepositoryUrl>https://dl.google.com/android/repository/ <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_macosx.zip"> osx Darwin + macosx aapt2 <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_linux.zip"> linux Linux + linux aapt2 <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_windows.zip"> windows + windows aapt2.exe - + + + + + + + + + + + + + + + + + <_BuildToolsHashMacOS>$(_BuildToolsHashMacOS.ToUpperInvariant()) + <_BuildToolsHashWindows>$(_BuildToolsHashWindows.ToUpperInvariant()) + <_BuildToolsHashLinux>$(_BuildToolsHashLinux.ToUpperInvariant()) + + + + + + + + + + + + + + + + + + + + + + + + Date: Mon, 18 May 2026 16:39:52 -0500 Subject: [PATCH 2/6] Use MSBuild target batching in aapt2.targets Refactor _DownloadBuildTools to use target batching via Outputs="...%(_BuildTools.Identity)" so the target body runs once per item. This replaces repeated DownloadFile/GetFileHash/Delete/Error calls with single instances that operate on the batched item. Also separate manifest download into its own target with a Condition to skip when all zips already exist. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/aapt2/aapt2.targets | 91 +++++++++++------------------------------ 1 file changed, 25 insertions(+), 66 deletions(-) diff --git a/src/aapt2/aapt2.targets b/src/aapt2/aapt2.targets index a0f30bf8da3..17550b72b5a 100644 --- a/src/aapt2/aapt2.targets +++ b/src/aapt2/aapt2.targets @@ -30,10 +30,8 @@ - - + - - - - - - - + + + + - + Query="//remotePackage[@path='build-tools;$(XABuildToolsFolder)']/archives/archive[host-os='%(_BuildTools.ManifestHostOs)']/complete/checksum/text()"> + - - - <_BuildToolsHashMacOS>$(_BuildToolsHashMacOS.ToUpperInvariant()) - <_BuildToolsHashWindows>$(_BuildToolsHashWindows.ToUpperInvariant()) - <_BuildToolsHashLinux>$(_BuildToolsHashLinux.ToUpperInvariant()) + <_ExpectedHash>$(_ExpectedHash.ToUpperInvariant()) - - - + - - - - - - - - - - - - - + + + From 3a13b23775838c12768e3d12a24a7b17eb3e5dce Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 18 May 2026 16:40:54 -0500 Subject: [PATCH 3/6] Remove unused per-platform XABuildToolsPackagePrefix properties These prefix properties were only used by Step_Get_Android_BuildTools which has been deleted. The property was always empty and served no purpose for current build-tools versions. Removed from: - KnownProperties.cs - Properties.Defaults.cs.in - xaprepare.targets - Configuration.props - AndroidToolchain.cs (inlined empty prefix away) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Configuration.props | 9 --------- .../xaprepare/xaprepare/Application/KnownProperties.cs | 4 ---- .../xaprepare/Application/Properties.Defaults.cs.in | 4 ---- .../ConfigAndData/Dependencies/AndroidToolchain.cs | 3 +-- build-tools/xaprepare/xaprepare/xaprepare.targets | 4 ---- 5 files changed, 1 insertion(+), 23 deletions(-) diff --git a/Configuration.props b/Configuration.props index 0508c786162..7786e76c125 100644 --- a/Configuration.props +++ b/Configuration.props @@ -108,15 +108,6 @@ armeabi-v7a;x86 arm64-v8a;x86_64 $(AllSupported32BitTargetAndroidAbis);$(AllSupported64BitTargetAndroidAbis) - - - - - $(XABuildToolsPackagePrefixMacOS) - $(XABuildToolsPackagePrefixWindows) 36 36.0.0 diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs index dd802f1ed26..ae97a055baf 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs @@ -53,10 +53,6 @@ static class KnownProperties public const string TestOutputDirectory = "TestOutputDirectory"; public const string XABuildToolsFolder = "XABuildToolsFolder"; public const string XABuildToolsVersion = "XABuildToolsVersion"; - public const string XABuildToolsPackagePrefixMacOS = "XABuildToolsPackagePrefixMacOS"; - public const string XABuildToolsPackagePrefixWindows = "XABuildToolsPackagePrefixWindows"; - public const string XABuildToolsPackagePrefixLinux = "XABuildToolsPackagePrefixLinux"; - public const string XABuildToolsPackagePrefix = "XABuildToolsPackagePrefix"; public const string XABinRelativeInstallPrefix = "XABinRelativeInstallPrefix"; public const string XAInstallPrefix = "XAInstallPrefix"; public const string XAPackagesDir = "XAPackagesDir"; diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in index 648a7ca1ed3..4b8ca664197 100644 --- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in +++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in @@ -57,10 +57,6 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.TestOutputDirectory, StripQuotes (@"@TestOutputDirectory@")); properties.Add (KnownProperties.XABuildToolsFolder, StripQuotes (@"@XABuildToolsFolder@")); properties.Add (KnownProperties.XABuildToolsVersion, StripQuotes ("@XABuildToolsVersion@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefixMacOS, StripQuotes ("@XABuildToolsPackagePrefixMacOS@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefixWindows, StripQuotes ("@XABuildToolsPackagePrefixWindows@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefixLinux, StripQuotes ("@XABuildToolsPackagePrefixLinux@")); - properties.Add (KnownProperties.XABuildToolsPackagePrefix, StripQuotes ("@XABuildToolsPackagePrefix@")); properties.Add (KnownProperties.XABinRelativeInstallPrefix, StripQuotes (@"@XABinRelativeInstallPrefix@")); properties.Add (KnownProperties.XAInstallPrefix, StripQuotes (@"@XAInstallPrefix@")); properties.Add (KnownProperties.XAPackagesDir, StripQuotes (@"@XAPackagesDir@")); diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs index 1c67c51d241..bb20004668c 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs @@ -25,7 +25,6 @@ public AndroidToolchain () string EmulatorPkgRevision = GetRequiredProperty (KnownProperties.EmulatorPkgRevision); string XABuildToolsFolder = GetRequiredProperty (KnownProperties.XABuildToolsFolder); string XABuildToolsVersion = GetRequiredProperty (KnownProperties.XABuildToolsVersion); - string XABuildToolsPackagePrefix = Context.Instance.Properties [KnownProperties.XABuildToolsPackagePrefix] ?? String.Empty; string XAPlatformToolsVersion = GetRequiredProperty (KnownProperties.XAPlatformToolsVersion); string XAPlatformToolsPackagePrefix = Context.Instance.Properties [KnownProperties.XAPlatformToolsPackagePrefix] ?? String.Empty; bool isArm64Apple = Context.Instance.OS.Flavor == "macOS" && RuntimeInformation.OSArchitecture == Architecture.Arm64; @@ -107,7 +106,7 @@ public AndroidToolchain () buildToolName: $"android-ndk-r{AndroidNdkVersion}", buildToolVersion: AndroidPkgRevision ), - new AndroidToolchainComponent ($"{XABuildToolsPackagePrefix}build-tools_r{XABuildToolsVersion}_{altOsTag}", + new AndroidToolchainComponent ($"build-tools_r{XABuildToolsVersion}_{altOsTag}", destDir: Path.Combine ("build-tools", XABuildToolsFolder), isMultiVersion: true, buildToolName: "android-sdk-build-tools", diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets index 158ae22440f..431d43a9176 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.targets +++ b/build-tools/xaprepare/xaprepare/xaprepare.targets @@ -91,10 +91,6 @@ - - - - From 9898a6827cc4317cdf5a02e15617f98919045164 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 10:30:58 -0500 Subject: [PATCH 4/6] Fix _DownloadAndroidManifest being skipped when zips are cached Remove the Condition on _DownloadAndroidManifest that checked whether zip files exist. When the zips are cached but _DownloadBuildTools Inputs/Outputs determines they are stale, the manifest target was being skipped (Exists=true) while _DownloadBuildTools still ran, causing XmlPeek to fail on the missing manifest file. The Inputs/Outputs on _DownloadBuildTools already handles the fully- cached case: if all zips are up-to-date, neither target runs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/aapt2/aapt2.targets | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aapt2/aapt2.targets b/src/aapt2/aapt2.targets index 17550b72b5a..bea2c379902 100644 --- a/src/aapt2/aapt2.targets +++ b/src/aapt2/aapt2.targets @@ -30,8 +30,7 @@ - + Date: Tue, 19 May 2026 10:32:37 -0500 Subject: [PATCH 5/6] Cache Android manifest download on incremental builds Use Inputs/Outputs on _DownloadAndroidManifest so it skips entirely when the manifest file exists and is newer than Configuration.props. This avoids any network requests on incremental builds, unlike SkipUnchangedFiles which still makes an HTTP round-trip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/aapt2/aapt2.targets | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/aapt2/aapt2.targets b/src/aapt2/aapt2.targets index bea2c379902..39d0742f938 100644 --- a/src/aapt2/aapt2.targets +++ b/src/aapt2/aapt2.targets @@ -30,12 +30,13 @@ - + From 2a414235c80bb72b3cfe7a15653e03e51acc2404 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 19 May 2026 12:23:40 -0500 Subject: [PATCH 6/6] Switch from SHA-1 to hardcoded SHA-256 for build-tools validation MSBuild GetFileHash only supports SHA-256 and above, but Google SDK manifest (repository2-3.xml) only provides SHA-1. Switch to hardcoded SHA-256 hashes in Configuration.props, following the same pattern used by bundletool (XABundleToolHash). This simplifies the targets by removing the manifest download target and XmlPeek logic entirely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Configuration.props | 3 +++ src/aapt2/aapt2.targets | 46 +++++++++++------------------------------ 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/Configuration.props b/Configuration.props index 7786e76c125..55aaa75cd94 100644 --- a/Configuration.props +++ b/Configuration.props @@ -110,6 +110,9 @@ $(AllSupported32BitTargetAndroidAbis);$(AllSupported64BitTargetAndroidAbis) 36 36.0.0 + 04E7F3A72044DE4926FA038FA0E251A37BBA1E1C3FB8BEAB6F8401BFD9EB4BF3 + 5D9AC77FB6FF43D9DA518A337B4FCF8F9097113DF531D99CCEFE80EF7CE8250B + AA1095CB14D83E483818A748A2C06FAAEB8E601561B06A356A119A1B2CA280D3 36.0.0 1.18.3 diff --git a/src/aapt2/aapt2.targets b/src/aapt2/aapt2.targets index 39d0742f938..51b8bfe27b6 100644 --- a/src/aapt2/aapt2.targets +++ b/src/aapt2/aapt2.targets @@ -3,78 +3,56 @@ 4.0.0-6051327 <_Destination>$(MicrosoftAndroidSdkOutDir) obj\$(Configuration)\ - <_AndroidManifestPath>$(AndroidToolchainCacheDirectory)\repository2-3.xml <_AndroidRepositoryUrl>https://dl.google.com/android/repository/ + <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_macosx.zip"> osx Darwin - macosx + $(XABuildToolsHashMacOS) aapt2 <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_linux.zip"> linux Linux - linux + $(XABuildToolsHashLinux) aapt2 <_BuildTools Include="build-tools_r$(XABuildToolsVersion)_windows.zip"> windows - windows + $(XABuildToolsHashWindows) aapt2.exe - - - - - - - - - - <_ExpectedHash>$(_ExpectedHash.ToUpperInvariant()) - - - - - +