From e293ce98476eed1ab5f2a802c91c35ebfb63cc1e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 10:31:18 -0500 Subject: [PATCH 01/13] [build] Move cmdline-tools, platform-tools, build-tools download to MSBuild Following the pattern established by PR #11399 (aapt2), migrate three more component groups from xaprepare's Step_Android_SDK_NDK into proper MSBuild targets in src/androidsdk/androidsdk.targets: * commandlinetools-{mac,linux,win}-* -> $(AndroidSdkDirectory)/cmdline-tools/$(CommandLineToolsFolder) * platform-tools_r$(XAPlatformToolsVersion)-{darwin,linux,win} -> $(AndroidSdkDirectory)/platform-tools * build-tools_r$(XABuildToolsVersion)_{macosx,linux,windows} -> $(AndroidSdkDirectory)/build-tools/$(XABuildToolsFolder) Each component group follows the same pattern: * Per-HostOS item with hardcoded SHA-256 (Google manifest only has SHA-1) * _Download target with target batching via Outputs="...%(Identity)" * _Extract target depending on _Download, using UnzipDirectoryChildren from $(BootstrapTasksAssembly) with TaskFactory="TaskHostFactory" Runtime="NET" * Inputs/Outputs on both targets for incremental build skip * source.properties used as the extraction sentinel file The _AcceptAndroidSdkLicenses target now depends on _ExtractCmdlineTools so the project is self-sufficient and does not require xaprepare to install cmdline-tools before sdkmanager is invoked. The build-tools full extract reuses the same cached zips that aapt2.targets downloads, but extracts the entire directory contents (versus just the aapt2 binary that aapt2.targets extracts into $(MicrosoftAndroidSdkOutDir) for the .NET SDK pack). ### Configuration.props Add SHA-256 hash properties for cmdline-tools and platform-tools. The build-tools hashes already existed from PR #11399. Remove the unused XAPlatformToolsPackagePrefix property; the only consumer was the corresponding entry in AndroidToolchain.cs, which is removed in this PR. ### xaprepare cleanup Remove the migrated entries from AndroidToolchain.cs, along with the now- unused cltOsTag/altOsTag/pltOsTag partial-class fields and the XAPlatformToolsPackagePrefix wiring in KnownProperties.cs, Properties.Defaults.cs.in, and xaprepare.targets. The remaining Step_Android_SDK_NDK responsibilities (NDK, cmake, emulator, system-images, ~25 platform APIs, m2repository, docs, source-36) will be migrated in follow-up commits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Configuration.props | 7 +- .../xaprepare/Application/KnownProperties.cs | 1 - .../Application/Properties.Defaults.cs.in | 1 - .../Dependencies/AndroidToolchain.Linux.cs | 3 - .../Dependencies/AndroidToolchain.MacOS.cs | 3 - .../Dependencies/AndroidToolchain.Windows.cs | 3 - .../Dependencies/AndroidToolchain.cs | 24 --- .../xaprepare/xaprepare/xaprepare.targets | 1 - src/androidsdk/androidsdk.targets | 171 +++++++++++++++++- 9 files changed, 176 insertions(+), 38 deletions(-) diff --git a/Configuration.props b/Configuration.props index fd4f45d1a38..3afb168cb59 100644 --- a/Configuration.props +++ b/Configuration.props @@ -111,8 +111,13 @@ 04E7F3A72044DE4926FA038FA0E251A37BBA1E1C3FB8BEAB6F8401BFD9EB4BF3 5D9AC77FB6FF43D9DA518A337B4FCF8F9097113DF531D99CCEFE80EF7CE8250B AA1095CB14D83E483818A748A2C06FAAEB8E601561B06A356A119A1B2CA280D3 - 36.0.0 + D3E9FA1DF3345CF728586908426615A60863D2632F73F1CE14F0F1349EF000FD + 0EAD642C943FFE79701FCCCA8F5F1C69C4CE4F43DF2EEFEE553F6CCB27CBFBE8 + 12C2841F354E92A0EB2FD7BF6F0F9BF8538ABCE7BD6B060AC8349D6F6A61107C + 5673201E6F3869F418EEED3B5CB6C4BE7401502BD0AAE1B12A29D164D647A54E + 7EC965280A073311C339E571CD5DE778B9975026CFCBE79F2B1CDCB1E15317EE + 98B565CB657B012DAE6794CEFC0F66AE1EFB4690C699B78A614B4A6A3505B003 1.18.3 A099CFA1543F55593BC2ED16A70A7C67FE54B1747BB7301F37FDFD6D91028E29 L_18.1.6-8.0.0-1 diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs index 8e40419bb0b..995e811511d 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs @@ -56,6 +56,5 @@ static class KnownProperties public const string XAInstallPrefix = "XAInstallPrefix"; public const string XAPackagesDir = "XAPackagesDir"; public const string XAPlatformToolsVersion = "XAPlatformToolsVersion"; - public const string XAPlatformToolsPackagePrefix = "XAPlatformToolsPackagePrefix"; } } diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in index cf6cee1fa49..c3d3b632cfa 100644 --- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in +++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in @@ -60,7 +60,6 @@ namespace Xamarin.Android.Prepare properties.Add (KnownProperties.XAInstallPrefix, StripQuotes (@"@XAInstallPrefix@")); properties.Add (KnownProperties.XAPackagesDir, StripQuotes (@"@XAPackagesDir@")); properties.Add (KnownProperties.XAPlatformToolsVersion, StripQuotes ("@XAPlatformToolsVersion@")); - properties.Add (KnownProperties.XAPlatformToolsPackagePrefix, StripQuotes ("@XAPlatformToolsPackagePrefix@")); } string StripQuotes (string input) diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs index acb50f06607..e540a5252e1 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs @@ -7,8 +7,5 @@ namespace Xamarin.Android.Prepare partial class AndroidToolchain { static readonly string osTag = "linux"; - static readonly string altOsTag = osTag; - static readonly string cltOsTag = osTag; - static readonly string pltOsTag = osTag; } } diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs index 53f41d1ad67..669b0b750b6 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs @@ -3,8 +3,5 @@ namespace Xamarin.Android.Prepare partial class AndroidToolchain { static readonly string osTag = "darwin"; - static readonly string altOsTag = "macosx"; - static readonly string cltOsTag = "mac"; - static readonly string pltOsTag = osTag; } } diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs index 6a4d379aa58..b3383b1684e 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs @@ -3,8 +3,5 @@ namespace Xamarin.Android.Prepare partial class AndroidToolchain { static readonly string osTag = "windows"; - static readonly string altOsTag = osTag; - static readonly string cltOsTag = "win"; - static readonly string pltOsTag = cltOsTag; } } diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs index bb20004668c..a3bdfc20923 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs @@ -19,14 +19,8 @@ public AndroidToolchain () string AndroidCmakeUrlPrefix = Context.Instance.Properties.GetValue (KnownProperties.AndroidCmakeUrlPrefix) ?? String.Empty; string AndroidCmakeVersion = GetRequiredProperty (KnownProperties.AndroidCmakeVersion); string AndroidCmakeVersionPath = GetRequiredProperty (KnownProperties.AndroidCmakeVersionPath); - string CommandLineToolsVersion = GetRequiredProperty (KnownProperties.CommandLineToolsVersion); - string CommandLineToolsFolder = GetRequiredProperty (KnownProperties.CommandLineToolsFolder); string EmulatorVersion = GetRequiredProperty (KnownProperties.EmulatorVersion); string EmulatorPkgRevision = GetRequiredProperty (KnownProperties.EmulatorPkgRevision); - string XABuildToolsFolder = GetRequiredProperty (KnownProperties.XABuildToolsFolder); - string XABuildToolsVersion = GetRequiredProperty (KnownProperties.XABuildToolsVersion); - 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; string emulatorArch = isArm64Apple ? "aarch64" : "x64"; string systemImageArch = isArm64Apple ? "arm64-v8a" : "x86_64"; @@ -106,24 +100,6 @@ public AndroidToolchain () buildToolName: $"android-ndk-r{AndroidNdkVersion}", buildToolVersion: AndroidPkgRevision ), - new AndroidToolchainComponent ($"build-tools_r{XABuildToolsVersion}_{altOsTag}", - destDir: Path.Combine ("build-tools", XABuildToolsFolder), - isMultiVersion: true, - buildToolName: "android-sdk-build-tools", - buildToolVersion: $"{XABuildToolsVersion}" - ), - new AndroidToolchainComponent ($"commandlinetools-{cltOsTag}-{CommandLineToolsVersion}", - destDir: Path.Combine ("cmdline-tools", CommandLineToolsFolder), - isMultiVersion: true, - buildToolName: "android-sdk-cmdline-tools", - buildToolVersion: $"{CommandLineToolsFolder}.{CommandLineToolsVersion}" - ), - new AndroidToolchainComponent ($"{XAPlatformToolsPackagePrefix}platform-tools_r{XAPlatformToolsVersion}-{pltOsTag}", - destDir: "platform-tools", - pkgRevision: XAPlatformToolsVersion, - buildToolName: "android-sdk-platform-tools", - buildToolVersion: XAPlatformToolsVersion - ), new AndroidToolchainComponent ($"emulator-{osTag}_{emulatorArch}-{EmulatorVersion}", destDir: "emulator", pkgRevision: EmulatorPkgRevision, diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets index 98a2eca9ee2..190d872b274 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.targets +++ b/build-tools/xaprepare/xaprepare/xaprepare.targets @@ -94,7 +94,6 @@ - + obj\$(Configuration)\ + <_AndroidRepositoryUrl>https://dl.google.com/android/repository/ <_SdkManagerDir>$(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder)\bin <_SdkManagerPath Condition=" '$(HostOS)' == 'Windows' ">$(_SdkManagerDir)\sdkmanager.bat <_SdkManagerPath Condition=" '$(HostOS)' != 'Windows' ">$(_SdkManagerDir)\sdkmanager <_LicensesAcceptedFile>$(AndroidSdkFullPath)\licenses\.licenses-accepted + <_CmdlineToolsDestination>$(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder) + <_PlatformToolsDestination>$(AndroidSdkFullPath)\platform-tools + <_BuildToolsDestination>$(AndroidSdkFullPath)\build-tools\$(XABuildToolsFolder) + + + + <_CmdlineToolsZip Include="commandlinetools-mac-$(CommandLineToolsVersion).zip" + Condition=" '$(HostOS)' == 'Darwin' "> + $(XACmdlineToolsHashMacOS) + + <_CmdlineToolsZip Include="commandlinetools-linux-$(CommandLineToolsVersion).zip" + Condition=" '$(HostOS)' == 'Linux' "> + $(XACmdlineToolsHashLinux) + + <_CmdlineToolsZip Include="commandlinetools-win-$(CommandLineToolsVersion).zip" + Condition=" '$(HostOS)' == 'Windows' "> + $(XACmdlineToolsHashWindows) + + + + <_PlatformToolsZip Include="platform-tools_r$(XAPlatformToolsVersion)-darwin.zip" + Condition=" '$(HostOS)' == 'Darwin' "> + $(XAPlatformToolsHashMacOS) + + <_PlatformToolsZip Include="platform-tools_r$(XAPlatformToolsVersion)-linux.zip" + Condition=" '$(HostOS)' == 'Linux' "> + $(XAPlatformToolsHashLinux) + + <_PlatformToolsZip Include="platform-tools_r$(XAPlatformToolsVersion)-win.zip" + Condition=" '$(HostOS)' == 'Windows' "> + $(XAPlatformToolsHashWindows) + + + + <_FullBuildToolsZip Include="build-tools_r$(XABuildToolsVersion)_macosx.zip" + Condition=" '$(HostOS)' == 'Darwin' "> + $(XABuildToolsHashMacOS) + + <_FullBuildToolsZip Include="build-tools_r$(XABuildToolsVersion)_linux.zip" + Condition=" '$(HostOS)' == 'Linux' "> + $(XABuildToolsHashLinux) + + <_FullBuildToolsZip Include="build-tools_r$(XABuildToolsVersion)_windows.zip" + Condition=" '$(HostOS)' == 'Windows' "> + $(XABuildToolsHashWindows) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + From f08bf1e371092d7128543451e731eefe62e90fb4 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 10:33:27 -0500 Subject: [PATCH 02/13] [build] Move cmake download to MSBuild Add cmake-$(AndroidCmakeVersion)-{darwin,linux,windows}.zip download/extract targets to src/androidsdk/androidsdk.targets following the same pattern as cmdline-tools/platform-tools/build-tools. The cmake zips have no top-level subdirectory, so NoSubdirectory="True" is used. Remove cmake from xaprepare's AndroidToolchain.cs and drop the now-unused AndroidCmakeUrlPrefix/AndroidCmakeVersion/AndroidCmakeVersionPath KnownProperties + Properties.Defaults.cs.in + xaprepare.targets wiring. Add XACmakeHash{MacOS,Linux,Windows} SHA-256 properties to Configuration.props. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Configuration.props | 3 ++ .../xaprepare/Application/KnownProperties.cs | 3 -- .../Application/Properties.Defaults.cs.in | 3 -- .../Dependencies/AndroidToolchain.cs | 14 +---- .../xaprepare/xaprepare/xaprepare.targets | 3 -- src/androidsdk/androidsdk.targets | 53 ++++++++++++++++++- 6 files changed, 55 insertions(+), 24 deletions(-) diff --git a/Configuration.props b/Configuration.props index 3afb168cb59..0514d606ebd 100644 --- a/Configuration.props +++ b/Configuration.props @@ -118,6 +118,9 @@ 5673201E6F3869F418EEED3B5CB6C4BE7401502BD0AAE1B12A29D164D647A54E 7EC965280A073311C339E571CD5DE778B9975026CFCBE79F2B1CDCB1E15317EE 98B565CB657B012DAE6794CEFC0F66AE1EFB4690C699B78A614B4A6A3505B003 + 57024D590691B9D66977D2F66786F10971549441B33B7830D3D2B659F80B93EA + 8CF0DC4F7CE0173D13ED71066FB6861A79C73545DF71D12CA3E933430F268D50 + 026982DBA2DBD2DE4A530CF1300FFAD05F6BC7D372D9DF83D46112FE017E0C6B 1.18.3 A099CFA1543F55593BC2ED16A70A7C67FE54B1747BB7301F37FDFD6D91028E29 L_18.1.6-8.0.0-1 diff --git a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs index 995e811511d..e80df2d8537 100644 --- a/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs +++ b/build-tools/xaprepare/xaprepare/Application/KnownProperties.cs @@ -2,9 +2,6 @@ namespace Xamarin.Android.Prepare { static class KnownProperties { - public const string AndroidCmakeUrlPrefix = "AndroidCmakeUrlPrefix"; - public const string AndroidCmakeVersion = "AndroidCmakeVersion"; - public const string AndroidCmakeVersionPath = "AndroidCmakeVersionPath"; public const string AndroidMinimumDotNetApiLevel = "AndroidMinimumDotNetApiLevel"; public const string AndroidDefaultTargetDotnetApiLevel = "AndroidDefaultTargetDotnetApiLevel"; public const string AndroidLatestStableApiLevel = "AndroidLatestStableApiLevel"; diff --git a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in index c3d3b632cfa..83ce47017ab 100644 --- a/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in +++ b/build-tools/xaprepare/xaprepare/Application/Properties.Defaults.cs.in @@ -6,9 +6,6 @@ namespace Xamarin.Android.Prepare { void InitDefaults () { - properties.Add (KnownProperties.AndroidCmakeUrlPrefix, StripQuotes ("@AndroidCmakeUrlPrefix@")); - properties.Add (KnownProperties.AndroidCmakeVersion, StripQuotes ("@AndroidCmakeVersion@")); - properties.Add (KnownProperties.AndroidCmakeVersionPath, StripQuotes (@"@AndroidCmakeVersionPath@")); properties.Add (KnownProperties.AndroidMinimumDotNetApiLevel, StripQuotes ("@AndroidMinimumDotNetApiLevel@")); properties.Add (KnownProperties.AndroidDefaultTargetDotnetApiLevel, StripQuotes ("@AndroidDefaultTargetDotnetApiLevel@")); properties.Add (KnownProperties.AndroidLatestStableApiLevel, StripQuotes ("@AndroidLatestStableApiLevel@")); diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs index a3bdfc20923..94f307e009f 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs @@ -16,12 +16,8 @@ public AndroidToolchain () string AndroidNdkVersion = BuildAndroidPlatforms.AndroidNdkVersion; string AndroidPkgRevision = BuildAndroidPlatforms.AndroidNdkPkgRevision; string AndroidNdkDirectory = GetRequiredProperty (KnownProperties.AndroidNdkDirectory); - string AndroidCmakeUrlPrefix = Context.Instance.Properties.GetValue (KnownProperties.AndroidCmakeUrlPrefix) ?? String.Empty; - string AndroidCmakeVersion = GetRequiredProperty (KnownProperties.AndroidCmakeVersion); - string AndroidCmakeVersionPath = GetRequiredProperty (KnownProperties.AndroidCmakeVersionPath); string EmulatorVersion = GetRequiredProperty (KnownProperties.EmulatorVersion); - string EmulatorPkgRevision = GetRequiredProperty (KnownProperties.EmulatorPkgRevision); - bool isArm64Apple = Context.Instance.OS.Flavor == "macOS" && RuntimeInformation.OSArchitecture == Architecture.Arm64; + string EmulatorPkgRevision = GetRequiredProperty (KnownProperties.EmulatorPkgRevision);bool isArm64Apple = Context.Instance.OS.Flavor == "macOS" && RuntimeInformation.OSArchitecture == Architecture.Arm64; string emulatorArch = isArm64Apple ? "aarch64" : "x64"; string systemImageArch = isArm64Apple ? "arm64-v8a" : "x86_64"; @@ -105,14 +101,6 @@ public AndroidToolchain () pkgRevision: EmulatorPkgRevision, dependencyType: AndroidToolchainComponentType.EmulatorDependency ), - new AndroidToolchainComponent ($"{AndroidCmakeUrlPrefix}cmake-{AndroidCmakeVersion}-{osTag}", - destDir: Path.Combine ("cmake", AndroidCmakeVersionPath), - isMultiVersion: true, - noSubdirectory: true, - pkgRevision: AndroidCmakeVersion, - buildToolName: "android-sdk-cmake", - buildToolVersion: AndroidCmakeVersion - ), }; } diff --git a/build-tools/xaprepare/xaprepare/xaprepare.targets b/build-tools/xaprepare/xaprepare/xaprepare.targets index 190d872b274..50f9c290493 100644 --- a/build-tools/xaprepare/xaprepare/xaprepare.targets +++ b/build-tools/xaprepare/xaprepare/xaprepare.targets @@ -42,9 +42,6 @@ - - - diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets index 04e438d7dd6..1ecdd94d8e7 100644 --- a/src/androidsdk/androidsdk.targets +++ b/src/androidsdk/androidsdk.targets @@ -10,6 +10,7 @@ <_CmdlineToolsDestination>$(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder) <_PlatformToolsDestination>$(AndroidSdkFullPath)\platform-tools <_BuildToolsDestination>$(AndroidSdkFullPath)\build-tools\$(XABuildToolsFolder) + <_CmakeDestination>$(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) + <_CmakeZip Include="cmake-$(AndroidCmakeVersion)-darwin.zip" + Condition=" '$(HostOS)' == 'Darwin' "> + $(XACmakeHashMacOS) + + <_CmakeZip Include="cmake-$(AndroidCmakeVersion)-linux.zip" + Condition=" '$(HostOS)' == 'Linux' "> + $(XACmakeHashLinux) + + <_CmakeZip Include="cmake-$(AndroidCmakeVersion)-windows.zip" + Condition=" '$(HostOS)' == 'Windows' "> + $(XACmakeHashWindows) + @@ -169,9 +184,43 @@ /> + + + + + + + + + + + + + + - + From 154ffaf6a15db4a54e8b13d3aca5672d1ff4e600 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 11:07:58 -0500 Subject: [PATCH 03/13] [build] Move emulator, system-image, platforms, m2repo, docs, source download to MSBuild Migrates the remaining $(AndroidSdkDirectory) components from xaprepare's `Step_Android_SDK_NDK` into `src/androidsdk/androidsdk.targets`: * Android emulator (per-HostOS and per-arch on Apple Silicon) * API 29 system image used by the emulator (per-HostOS, with the arm64-v8a variant on Apple Silicon) * 25 platform APIs (android-2.3.3..platform-37.0_r01) * `android_m2repository_r47`, `docs-24_r01`, `source-36_r01` Refactors the per-component download/extract pattern (previously four near-identical 30-line target pairs) into a single `_AndroidSdkPackage` ItemGroup with metadata (`Hash`, `Destination`, `NoSubdirectory`, `Url`, `GeneratePackageXml`) batched through: * `_DownloadAndroidSdkPackages` - downloads the zip, validates SHA-256, deletes + errors on mismatch. * `_ExtractAndroidSdkPackages` - unzips with `UnzipDirectoryChildren`, using `source.properties` as the per-component incremental sentinel. * `_GenerateAndroidPackageXmls` - calls the new `GenerateAndroidPackageXml` BootstrapTask (the MSBuild port of `Step_Android_SDK_NDK.WritePackageXmls`) to write the synthetic `emulator/package.xml`. Adds a `$(AndroidSdkPlatforms)` MSBuild property (default `latest`, accepts `all` or a comma-separated list of API levels) that mirrors xaprepare's `--android-sdk-platforms` flag. Platform items are filtered into `_AndroidSdkPackage` by a `BeforeTargets` target (`%()` metadata cannot be used in conditions on `Include="@(... -> ...)"` transforms). Adds 14 SHA-256 hash properties to `Configuration.props` for the new zips; Google's manifest only ships SHA-1 which `` does not support. `AndroidToolchain.cs` now contains only the NDK entry; the per-OS partials retain only `osTag`. Everything else under `$(AndroidSdkDirectory)` is handled by MSBuild. Validated end-to-end on Windows: 30 components, 51,483 files, 6.087 GB extract from a cold cache; `emulator/package.xml` generated with the correct schema and `EmulatorPkgRevision=36.4.10`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Configuration.props | 14 + .../GenerateAndroidPackageXml.cs | 77 ++++ .../Dependencies/AndroidToolchain.cs | 63 +-- src/androidsdk/androidsdk.targets | 401 +++++++++++------- 4 files changed, 333 insertions(+), 222 deletions(-) create mode 100644 build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs diff --git a/Configuration.props b/Configuration.props index 0514d606ebd..14226e83bb9 100644 --- a/Configuration.props +++ b/Configuration.props @@ -121,6 +121,20 @@ 57024D590691B9D66977D2F66786F10971549441B33B7830D3D2B659F80B93EA 8CF0DC4F7CE0173D13ED71066FB6861A79C73545DF71D12CA3E933430F268D50 026982DBA2DBD2DE4A530CF1300FFAD05F6BC7D372D9DF83D46112FE017E0C6B + + 3029489D4B2F868DC5347AA27B1E52FB48D6D28ABD51278E3FBEC2EE73201EDF + EDA85B9BFFD0926B7A01EA334A30AB41827F573CBDE8F7F97FCD8C5B38F039F6 + 58BFDF0572F06EC007A7287154CDC6B2EBFEA947D07A1E26A0F6B5E1A39E9191 + 9F2855BFE98E80728E61D45B0EA72BA22F64952C9330559FA4E2CD3698A32231 + + B5C3FDA1F4B4931C30518D342E4AD5F7464945E0CDCED3538D4FF2E12F7BF201 + 7FD1610E16A69AC2E202278E82077DE134CE404FE099D872019F69F3A340ACDD + B5C3FDA1F4B4931C30518D342E4AD5F7464945E0CDCED3538D4FF2E12F7BF201 + B5C3FDA1F4B4931C30518D342E4AD5F7464945E0CDCED3538D4FF2E12F7BF201 + + A3F91808DCE50C1717737DE90C18479ED3A78B147E06985247D138E7AB5123D0 + 68DB2690CB92E4EE5373AC9B792642C90717D8F417D83ECCEA48781171B3182A + BD97900346A70C784AC8B15C809539DDEF34ED3B5BE1DF8A9A89CF298BE93798 1.18.3 A099CFA1543F55593BC2ED16A70A7C67FE54B1747BB7301F37FDFD6D91028E29 L_18.1.6-8.0.0-1 diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs new file mode 100644 index 00000000000..8acdffa65a5 --- /dev/null +++ b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs @@ -0,0 +1,77 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tools.BootstrapTasks +{ + // Writes a `package.xml` next to an Android SDK component's `source.properties`, + // matching the format produced by xaprepare's `Step_Android_SDK_NDK.WritePackageXmls()`. + // Used for components (e.g. the emulator) where the upstream zip omits `package.xml` but + // the Android SDK manager expects one to be present after install. + public class GenerateAndroidPackageXml : Task + { + static readonly XNamespace AndroidRepositoryCommon = "http://schemas.android.com/repository/android/common/01"; + static readonly XNamespace AndroidRepositoryGeneric = "http://schemas.android.com/repository/android/generic/01"; + + [Required] + public ITaskItem [] Directories { get; set; } = []; + + public override bool Execute () + { + foreach (var dir in Directories) { + string path = dir.ItemSpec; + var properties = ReadSourceProperties (path); + if (properties == null) { + Log.LogMessage (MessageImportance.Low, $"Skipping '{path}', no source.properties file found."); + continue; + } + string packageXml = Path.Combine (path, "package.xml"); + Log.LogMessage (MessageImportance.Low, $"Writing '{packageXml}'"); + var doc = new XDocument ( + new XElement (AndroidRepositoryCommon + "repository", + new XAttribute (XNamespace.Xmlns + "ns2", AndroidRepositoryCommon.NamespaceName), + new XAttribute (XNamespace.Xmlns + "ns3", AndroidRepositoryGeneric.NamespaceName), + new XElement ("localPackage", + new XAttribute ("path", properties ["Pkg.Path"]), + new XAttribute ("obsolete", "false"), + new XElement ("revision", GetRevision (properties ["Pkg.Revision"])), + new XElement ("display-name", properties ["Pkg.Desc"])))); + doc.Save (packageXml, SaveOptions.None); + } + + return !Log.HasLoggedErrors; + } + + static Dictionary? ReadSourceProperties (string dir) + { + var path = Path.Combine (dir, "source.properties"); + if (!File.Exists (path)) + return null; + var dict = new Dictionary (); + foreach (var line in File.ReadLines (path)) { + if (line.Length == 0) + continue; + var entry = line.Split (new [] { '=' }, 2, StringSplitOptions.None); + if (entry.Length != 2) + continue; + dict.Add (entry [0], entry [1]); + } + return dict; + } + + static IEnumerable GetRevision (string revision) + { + var parts = revision.Split ('.'); + if (parts.Length > 0) + yield return new XElement ("major", parts [0]); + if (parts.Length > 1) + yield return new XElement ("minor", parts [1]); + if (parts.Length > 2) + yield return new XElement ("micro", parts [2]); + } + } +} diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs index 94f307e009f..d1937e1427e 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.InteropServices; namespace Xamarin.Android.Prepare { @@ -16,10 +15,6 @@ public AndroidToolchain () string AndroidNdkVersion = BuildAndroidPlatforms.AndroidNdkVersion; string AndroidPkgRevision = BuildAndroidPlatforms.AndroidNdkPkgRevision; string AndroidNdkDirectory = GetRequiredProperty (KnownProperties.AndroidNdkDirectory); - string EmulatorVersion = GetRequiredProperty (KnownProperties.EmulatorVersion); - string EmulatorPkgRevision = GetRequiredProperty (KnownProperties.EmulatorPkgRevision);bool isArm64Apple = Context.Instance.OS.Flavor == "macOS" && RuntimeInformation.OSArchitecture == Architecture.Arm64; - string emulatorArch = isArm64Apple ? "aarch64" : "x64"; - string systemImageArch = isArm64Apple ? "arm64-v8a" : "x86_64"; // Upstream manifests with version information: // @@ -38,69 +33,15 @@ public AndroidToolchain () // https://dl-ssl.google.com/android/repository/sys-img/google_apis/sys-img2-1.xml // * system images // - // Note "isLatestStable" is a bad name, it's actually "xaprepare should install this API by default" + // Everything that lives under $(AndroidSdkDirectory) is downloaded by + // `src/androidsdk/androidsdk.targets`. Only the NDK remains here. Components = new List { - new AndroidPlatformComponent ("android-2.3.3_r02", apiLevel: "10", pkgRevision: "2"), - new AndroidPlatformComponent ("android-15_r05", apiLevel: "15", pkgRevision: "5"), - new AndroidPlatformComponent ("android-16_r05", apiLevel: "16", pkgRevision: "5"), - new AndroidPlatformComponent ("android-17_r03", apiLevel: "17", pkgRevision: "3"), - new AndroidPlatformComponent ("android-18_r03", apiLevel: "18", pkgRevision: "3"), - new AndroidPlatformComponent ("android-19_r04", apiLevel: "19", pkgRevision: "4"), - new AndroidPlatformComponent ("android-20_r02", apiLevel: "20", pkgRevision: "2"), - new AndroidPlatformComponent ("android-21_r02", apiLevel: "21", pkgRevision: "2"), - new AndroidPlatformComponent ("android-22_r02", apiLevel: "22", pkgRevision: "2"), - new AndroidPlatformComponent ("platform-23_r03", apiLevel: "23", pkgRevision: "3"), - new AndroidPlatformComponent ("platform-24_r02", apiLevel: "24", pkgRevision: "3"), // Local package revision is actually .3 - new AndroidPlatformComponent ("platform-25_r03", apiLevel: "25", pkgRevision: "3"), - new AndroidPlatformComponent ("platform-26_r02", apiLevel: "26", pkgRevision: "2"), - new AndroidPlatformComponent ("platform-27_r03", apiLevel: "27", pkgRevision: "3"), - new AndroidPlatformComponent ("platform-28_r04", apiLevel: "28", pkgRevision: "4"), - new AndroidPlatformComponent ("platform-29_r01", apiLevel: "29", pkgRevision: "1"), - new AndroidPlatformComponent ("platform-30_r01", apiLevel: "30", pkgRevision: "1"), - new AndroidPlatformComponent ("platform-31_r01", apiLevel: "31", pkgRevision: "1"), - new AndroidPlatformComponent ("platform-32_r01", apiLevel: "32", pkgRevision: "1"), - new AndroidPlatformComponent ("platform-33-ext3_r03", apiLevel: "33", pkgRevision: "3"), - new AndroidPlatformComponent ("platform-34-ext7_r02", apiLevel: "34", pkgRevision: "2"), - new AndroidPlatformComponent ("platform-35_r02", apiLevel: "35", pkgRevision: "2"), - new AndroidPlatformComponent ("platform-36_r02", apiLevel: "36", pkgRevision: "2"), - new AndroidPlatformComponent ("platform-36.1_r01", apiLevel: "36.1", pkgRevision: "1"), - new AndroidPlatformComponent ("platform-37.0_r01", apiLevel: "37.0", pkgRevision: "1", isLatestStable: true), - - new AndroidToolchainComponent ("source-36_r01", - destDir: Path.Combine ("sources", "android-36"), - pkgRevision: "1", - dependencyType: AndroidToolchainComponentType.BuildDependency, - buildToolVersion: "36.1" - ), - new AndroidToolchainComponent ("docs-24_r01", - destDir: "docs", - pkgRevision: "1", - dependencyType: AndroidToolchainComponentType.BuildDependency, - buildToolVersion: "24.1" - ), - new AndroidToolchainComponent ("android_m2repository_r47", - destDir: Path.Combine ("extras", "android", "m2repository"), - pkgRevision: "47.0.0", - dependencyType: AndroidToolchainComponentType.BuildDependency, - buildToolVersion: "47.0.0" - ), - new AndroidToolchainComponent (isArm64Apple ? $"{systemImageArch}-29_r08" : $"{systemImageArch}-29_r08-{osTag}", - destDir: Path.Combine ("system-images", "android-29", "default", systemImageArch), - relativeUrl: new Uri ("sys-img/android/", UriKind.Relative), - pkgRevision: "8", - dependencyType: AndroidToolchainComponentType.EmulatorDependency - ), new AndroidToolchainComponent ($"android-ndk-r{AndroidNdkVersion}-{osTag}", destDir: AndroidNdkDirectory, pkgRevision: AndroidPkgRevision, buildToolName: $"android-ndk-r{AndroidNdkVersion}", buildToolVersion: AndroidPkgRevision ), - new AndroidToolchainComponent ($"emulator-{osTag}_{emulatorArch}-{EmulatorVersion}", - destDir: "emulator", - pkgRevision: EmulatorPkgRevision, - dependencyType: AndroidToolchainComponentType.EmulatorDependency - ), }; } diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets index 1ecdd94d8e7..bdd695671d1 100644 --- a/src/androidsdk/androidsdk.targets +++ b/src/androidsdk/androidsdk.targets @@ -1,5 +1,16 @@ + + obj\$(Configuration)\ <_AndroidRepositoryUrl>https://dl.google.com/android/repository/ @@ -7,225 +18,293 @@ <_SdkManagerPath Condition=" '$(HostOS)' == 'Windows' ">$(_SdkManagerDir)\sdkmanager.bat <_SdkManagerPath Condition=" '$(HostOS)' != 'Windows' ">$(_SdkManagerDir)\sdkmanager <_LicensesAcceptedFile>$(AndroidSdkFullPath)\licenses\.licenses-accepted - <_CmdlineToolsDestination>$(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder) - <_PlatformToolsDestination>$(AndroidSdkFullPath)\platform-tools - <_BuildToolsDestination>$(AndroidSdkFullPath)\build-tools\$(XABuildToolsFolder) - <_CmakeDestination>$(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) + + + <_HostArch>$([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture) + <_IsArm64Apple Condition=" '$(HostOS)' == 'Darwin' and '$(_HostArch)' == 'Arm64' ">true + <_EmulatorArch Condition=" '$(_IsArm64Apple)' == 'true' ">aarch64 + <_EmulatorArch Condition=" '$(_EmulatorArch)' == '' ">x64 + <_SystemImageArch Condition=" '$(_IsArm64Apple)' == 'true' ">arm64-v8a + <_SystemImageArch Condition=" '$(_SystemImageArch)' == '' ">x86_64 + + + latest + <_InstallAllPlatforms Condition=" '$(AndroidSdkPlatforms.ToLower())' == 'all' ">true + <_InstallLatestPlatformOnly Condition=" '$(AndroidSdkPlatforms.ToLower())' == 'latest' ">true + <_RequestedApiLevels>;$(AndroidSdkPlatforms.Replace(',', ';')); + + + + + <_PlatformPackage Include="android-2.3.3_r02"> 10 54BDB0F1CA06BA5747061DDEEA20F431AF72C448334FD4D3D7F84EA2CCD29FEA + <_PlatformPackage Include="android-15_r05"> 15 5BC1F93AAE86B4336FFC4CAE9EB8EC41A9A8FD677582DD86A9629798F019BED9 + <_PlatformPackage Include="android-16_r05"> 16 FD7F269A423D1F1D079EABF9F918CEAB49108702A1C6BB2589D57C23393503D3 + <_PlatformPackage Include="android-17_r03"> 17 B66E73FB2639F8C916FDE4369AA29012A5C531E156DBB205FE3788FE998FBBE8 + <_PlatformPackage Include="android-18_r03"> 18 166AE9CF299747A5FAA8F04168F0EE47CD7466A975D8B44ACAAA62A43E767568 + <_PlatformPackage Include="android-19_r04"> 19 5EFC3A3A682C1D49128DADDB6716C433EDF16E63349F32959B6207524AC04039 + <_PlatformPackage Include="android-20_r02"> 20 EF08C453E16AB6E656CF5D9413EF61CB8C650607D33B24EE4CE08DAFDFE965A7 + <_PlatformPackage Include="android-21_r02"> 21 A76CD7AD3080AC6CE9F037CB935B399A1BAD396C0605D4FF42F693695F1DCEFE + <_PlatformPackage Include="android-22_r02"> 22 45EB581BBE53C9256F34C26B2CEA919543C0079140897AC721CF88C0B9F6789E + <_PlatformPackage Include="platform-23_r03"> 23 4B4BCDDEAD3319708275C54C76294707BFAA953D767E34F1A5B599F3EDD0076C + <_PlatformPackage Include="platform-24_r02"> 24 F268F5945C6ECE7EA95C1C252067280854D2A20DA924E22AE4720287DF8BDBC9 + <_PlatformPackage Include="platform-25_r03"> 25 9B742D34590FE73FB7229E34835ECFFB1846CA389D9F924F0B2A37DE525DC6B8 + <_PlatformPackage Include="platform-26_r02"> 26 2AAFA7D19C5E9C4B643EE6ADE3D85EF89DC2F79E8383EFDB9BAF7FDDAD74B52A + <_PlatformPackage Include="platform-27_r03"> 27 020C4C090BC82CE87EBAAE5D1A922E21B39A1D03C78FFA43F0C3E42FC7D28169 + <_PlatformPackage Include="platform-28_r04"> 28 967F8CA99B71D337AA9C7781F9D65DD8110BF5C4746F44304047BF71B20D0DED + <_PlatformPackage Include="platform-29_r01"> 29 2C9E961858E03900FFA0801A4F6234AA7223363B629FD8E1BE60DA9AA09D86BD + <_PlatformPackage Include="platform-30_r01"> 30 337B1B0E202A02F90DA2E719305F157FCCDA215C760F5B8F7121ACEDCA44942B + <_PlatformPackage Include="platform-31_r01"> 31 1D69FE1D7F9788D82FF3A374FAF4F6CCC9D1D372AA84A86B5BCFB517523B0B3F + <_PlatformPackage Include="platform-32_r01"> 32 01D8DA1C900E70FCF5DA39767D5444E39928935B1A5927055CE749FC348CA7AE + <_PlatformPackage Include="platform-33-ext3_r03"> 33 B32B10F787867987F03AE8E101D217E053A9065B7136379FB353B388379AED1D + <_PlatformPackage Include="platform-34-ext7_r02"> 34 5323311CC3E4AD614F0B8053C72B651726F3422448CEDD39E48F00737CDA8AD0 + <_PlatformPackage Include="platform-35_r02"> 35 0988CACAD01B38A18A47BAC14A0695F246BC76C1B06C0EEB8EB0DC825AB0C8E0 + <_PlatformPackage Include="platform-36_r02"> 36 37607369A28C5B640B3A7998868D45898EBCB777565A0E85F9ACF36F29631D2E + <_PlatformPackage Include="platform-36.1_r01"> 36.1 265E9AA7D8DB6ABE7AD1696085D5D62784B341084C625DE46DB530970B1E806D + <_PlatformPackage Include="platform-37.0_r01"> 37.0 true 19BDCF42DE0CB0E9500A27DA6833FC30CDD49A7EC690AA5CABAA0EF893AF9EBE + + + - <_CmdlineToolsZip Include="commandlinetools-mac-$(CommandLineToolsVersion).zip" - Condition=" '$(HostOS)' == 'Darwin' "> + <_AndroidSdkPackage Include="commandlinetools-mac-$(CommandLineToolsVersion).zip" Condition=" '$(HostOS)' == 'Darwin' "> $(XACmdlineToolsHashMacOS) - - <_CmdlineToolsZip Include="commandlinetools-linux-$(CommandLineToolsVersion).zip" - Condition=" '$(HostOS)' == 'Linux' "> + $(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder) + + <_AndroidSdkPackage Include="commandlinetools-linux-$(CommandLineToolsVersion).zip" Condition=" '$(HostOS)' == 'Linux' "> $(XACmdlineToolsHashLinux) - - <_CmdlineToolsZip Include="commandlinetools-win-$(CommandLineToolsVersion).zip" - Condition=" '$(HostOS)' == 'Windows' "> + $(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder) + + <_AndroidSdkPackage Include="commandlinetools-win-$(CommandLineToolsVersion).zip" Condition=" '$(HostOS)' == 'Windows' "> $(XACmdlineToolsHashWindows) - + $(AndroidSdkFullPath)\cmdline-tools\$(CommandLineToolsFolder) + - <_PlatformToolsZip Include="platform-tools_r$(XAPlatformToolsVersion)-darwin.zip" - Condition=" '$(HostOS)' == 'Darwin' "> + <_AndroidSdkPackage Include="platform-tools_r$(XAPlatformToolsVersion)-darwin.zip" Condition=" '$(HostOS)' == 'Darwin' "> $(XAPlatformToolsHashMacOS) - - <_PlatformToolsZip Include="platform-tools_r$(XAPlatformToolsVersion)-linux.zip" - Condition=" '$(HostOS)' == 'Linux' "> + $(AndroidSdkFullPath)\platform-tools + + <_AndroidSdkPackage Include="platform-tools_r$(XAPlatformToolsVersion)-linux.zip" Condition=" '$(HostOS)' == 'Linux' "> $(XAPlatformToolsHashLinux) - - <_PlatformToolsZip Include="platform-tools_r$(XAPlatformToolsVersion)-win.zip" - Condition=" '$(HostOS)' == 'Windows' "> + $(AndroidSdkFullPath)\platform-tools + + <_AndroidSdkPackage Include="platform-tools_r$(XAPlatformToolsVersion)-win.zip" Condition=" '$(HostOS)' == 'Windows' "> $(XAPlatformToolsHashWindows) - + $(AndroidSdkFullPath)\platform-tools + - <_FullBuildToolsZip Include="build-tools_r$(XABuildToolsVersion)_macosx.zip" - Condition=" '$(HostOS)' == 'Darwin' "> + <_AndroidSdkPackage Include="build-tools_r$(XABuildToolsVersion)_macosx.zip" Condition=" '$(HostOS)' == 'Darwin' "> $(XABuildToolsHashMacOS) - - <_FullBuildToolsZip Include="build-tools_r$(XABuildToolsVersion)_linux.zip" - Condition=" '$(HostOS)' == 'Linux' "> + $(AndroidSdkFullPath)\build-tools\$(XABuildToolsFolder) + + <_AndroidSdkPackage Include="build-tools_r$(XABuildToolsVersion)_linux.zip" Condition=" '$(HostOS)' == 'Linux' "> $(XABuildToolsHashLinux) - - <_FullBuildToolsZip Include="build-tools_r$(XABuildToolsVersion)_windows.zip" - Condition=" '$(HostOS)' == 'Windows' "> + $(AndroidSdkFullPath)\build-tools\$(XABuildToolsFolder) + + <_AndroidSdkPackage Include="build-tools_r$(XABuildToolsVersion)_windows.zip" Condition=" '$(HostOS)' == 'Windows' "> $(XABuildToolsHashWindows) - + $(AndroidSdkFullPath)\build-tools\$(XABuildToolsFolder) + - <_CmakeZip Include="cmake-$(AndroidCmakeVersion)-darwin.zip" - Condition=" '$(HostOS)' == 'Darwin' "> + <_AndroidSdkPackage Include="cmake-$(AndroidCmakeVersion)-darwin.zip" Condition=" '$(HostOS)' == 'Darwin' "> $(XACmakeHashMacOS) - - <_CmakeZip Include="cmake-$(AndroidCmakeVersion)-linux.zip" - Condition=" '$(HostOS)' == 'Linux' "> + $(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) + true + + <_AndroidSdkPackage Include="cmake-$(AndroidCmakeVersion)-linux.zip" Condition=" '$(HostOS)' == 'Linux' "> $(XACmakeHashLinux) - - <_CmakeZip Include="cmake-$(AndroidCmakeVersion)-windows.zip" - Condition=" '$(HostOS)' == 'Windows' "> + $(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) + true + + <_AndroidSdkPackage Include="cmake-$(AndroidCmakeVersion)-windows.zip" Condition=" '$(HostOS)' == 'Windows' "> $(XACmakeHashWindows) - - + $(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) + true + - + + <_AndroidSdkPackage Include="emulator-darwin_x64-$(EmulatorVersion).zip" Condition=" '$(HostOS)' == 'Darwin' and '$(_IsArm64Apple)' != 'true' "> + $(XAEmulatorHashMacOSx64) + $(AndroidSdkFullPath)\emulator + true + + <_AndroidSdkPackage Include="emulator-darwin_aarch64-$(EmulatorVersion).zip" Condition=" '$(_IsArm64Apple)' == 'true' "> + $(XAEmulatorHashMacOSArm64) + $(AndroidSdkFullPath)\emulator + true + + <_AndroidSdkPackage Include="emulator-linux_x64-$(EmulatorVersion).zip" Condition=" '$(HostOS)' == 'Linux' "> + $(XAEmulatorHashLinux) + $(AndroidSdkFullPath)\emulator + true + + <_AndroidSdkPackage Include="emulator-windows_x64-$(EmulatorVersion).zip" Condition=" '$(HostOS)' == 'Windows' "> + $(XAEmulatorHashWindows) + $(AndroidSdkFullPath)\emulator + true + - - - - - - - - - + + <_AndroidSdkPackage Include="x86_64-29_r08-darwin.zip" Condition=" '$(HostOS)' == 'Darwin' and '$(_IsArm64Apple)' != 'true' "> + sys-img/android/x86_64-29_r08-darwin.zip + $(XASystemImageHashMacOSx64) + $(AndroidSdkFullPath)\system-images\android-29\default\x86_64 + + <_AndroidSdkPackage Include="arm64-v8a-29_r08.zip" Condition=" '$(_IsArm64Apple)' == 'true' "> + sys-img/android/arm64-v8a-29_r08.zip + $(XASystemImageHashMacOSArm64) + $(AndroidSdkFullPath)\system-images\android-29\default\arm64-v8a + + <_AndroidSdkPackage Include="x86_64-29_r08-linux.zip" Condition=" '$(HostOS)' == 'Linux' "> + sys-img/android/x86_64-29_r08-linux.zip + $(XASystemImageHashLinux) + $(AndroidSdkFullPath)\system-images\android-29\default\x86_64 + + <_AndroidSdkPackage Include="x86_64-29_r08-windows.zip" Condition=" '$(HostOS)' == 'Windows' "> + sys-img/android/x86_64-29_r08-windows.zip + $(XASystemImageHashWindows) + $(AndroidSdkFullPath)\system-images\android-29\default\x86_64 + - - - + + <_AndroidSdkPackage Include="android_m2repository_r47.zip"> + $(XAAndroidM2RepositoryHash) + $(AndroidSdkFullPath)\extras\android\m2repository + + <_AndroidSdkPackage Include="docs-24_r01.zip"> + $(XAAndroidDocsHash) + $(AndroidSdkFullPath)\docs + + <_AndroidSdkPackage Include="source-36_r01.zip"> + $(XAAndroidSourcesHash) + $(AndroidSdkFullPath)\sources\android-36 + - - - - - - - - - + + - - + + + <_AndroidSdkPackage Include="@(_PlatformPackage->'%(Identity).zip')" + Condition=" '$(_InstallAllPlatforms)' == 'true' or + ('$(_InstallLatestPlatformOnly)' == 'true' and '%(_PlatformPackage.IsLatestStable)' == 'true') or + $(_RequestedApiLevels.Contains(';%(_PlatformPackage.ApiLevel);')) "> + %(_PlatformPackage.Hash) + $(AndroidSdkFullPath)\platforms\android-%(_PlatformPackage.ApiLevel) + + - - + + Outputs="$(AndroidToolchainCacheDirectory)\%(_AndroidSdkPackage.Identity)"> - - - - - - - - - - - - - - + - + + + + + + <_PackageXmlDirectory Include="@(_AndroidSdkPackage->'%(Destination)')" + Condition=" '%(_AndroidSdkPackage.GeneratePackageXml)' == 'true' " /> + + + + - + From 47105b4cc4d73bb00cc835e754f04544e6d18edf Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 11:24:07 -0500 Subject: [PATCH 04/13] [xaprepare] Remove dead Android SDK code paths Now that all $(AndroidSdkDirectory) components are downloaded by src/androidsdk/androidsdk.targets, the supporting code in xaprepare is dead and can be removed: * AndroidPlatformComponent class * Context.AndroidSdkPlatforms and Main.ParseAndroidSdkPlatformLevels * --android-sdk-platforms CLI flag * Step_Android_SDK_NDK.WritePackageXmls / ReadSourceProperties / GetRevision / XNamespace constants (replaced by the GenerateAndroidPackageXml BootstrapTask) * ShouldInstall (AndroidPlatformComponent ...) and the corresponding branch in Check () setup-test-environment-steps.yaml now passes the platform list to androidsdk.csproj via -p:AndroidSdkPlatforms= instead of to xaprepare. manifest-attribute-codegen/README.md updated to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../setup-test-environment-steps.yaml | 4 +- .../manifest-attribute-codegen/README.md | 7 +- .../Application/AndroidToolchainComponent.cs | 16 ---- .../xaprepare/Application/Context.cs | 5 -- build-tools/xaprepare/xaprepare/Main.cs | 16 ---- .../xaprepare/Steps/Step_Android_SDK_NDK.cs | 81 ------------------- 6 files changed, 6 insertions(+), 123 deletions(-) diff --git a/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml b/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml index 2f1a0b73b81..99d1f4f559c 100644 --- a/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml +++ b/build-tools/automation/yaml-templates/setup-test-environment-steps.yaml @@ -52,7 +52,7 @@ steps: - template: /build-tools/automation/yaml-templates/run-xaprepare.yaml parameters: - arguments: --s=${{ parameters.xaprepareScenario }} --android-sdk-platforms="${{ parameters.androidSdkPlatforms }}" + arguments: --s=${{ parameters.xaprepareScenario }} xaSourcePath: ${{ parameters.xaSourcePath }} - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml @@ -60,7 +60,7 @@ steps: displayName: install OpenJDK and accept Android SDK licenses xaSourcePath: ${{ parameters.xaSourcePath }} project: ${{ parameters.xaSourcePath }}/src/androidsdk/androidsdk.csproj - arguments: -c ${{ parameters.configuration }} -bl:${{ parameters.xaSourcePath }}/bin/Test${{ parameters.configuration }}/androidsdk.binlog + arguments: -c ${{ parameters.configuration }} -p:AndroidSdkPlatforms="${{ parameters.androidSdkPlatforms }}" -bl:${{ parameters.xaSourcePath }}/bin/Test${{ parameters.configuration }}/androidsdk.binlog continueOnError: false - task: DotNetCoreCLI@2 diff --git a/build-tools/manifest-attribute-codegen/README.md b/build-tools/manifest-attribute-codegen/README.md index 168d68a9144..e17ff3a81a9 100644 --- a/build-tools/manifest-attribute-codegen/README.md +++ b/build-tools/manifest-attribute-codegen/README.md @@ -15,11 +15,12 @@ This generally only needs to be done each time we bind a new Android API level. # How to use -Ensure all Android platform SDK levels are installed. This can be done with `xaprepare`; -run the following command from the checkout toplevel: +Ensure all Android platform SDK levels are installed. This is done via the +`androidsdk.csproj` project; run the following command from the checkout +toplevel: ```dotnetcli -dotnet run --project "build-tools/xaprepare/xaprepare/xaprepare.csproj" -- -s AndroidTestDependencies --android-sdk-platforms=all +dotnet build src/androidsdk/androidsdk.csproj -p:AndroidSdkPlatforms=all ``` Next, from this directory, run: diff --git a/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs b/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs index a03baee3342..787a6bd0f2c 100644 --- a/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs +++ b/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs @@ -1,5 +1,4 @@ using System; -using System.IO; namespace Xamarin.Android.Prepare { @@ -42,21 +41,6 @@ public void AddToInventory () } } - class AndroidPlatformComponent : AndroidToolchainComponent - { - public string ApiLevel { get; } - public bool IsLatestStable { get; } - public bool IsPreview { get; } - - public AndroidPlatformComponent (string name, string apiLevel, string pkgRevision, bool isLatestStable = false, bool isPreview = false) - : base (name, Path.Combine ("platforms", $"android-{apiLevel}"), pkgRevision: pkgRevision, buildToolName: $"android-sdk-{name}", buildToolVersion: $"{apiLevel}.{pkgRevision}") - { - ApiLevel = apiLevel; - IsLatestStable = isLatestStable; - IsPreview = isPreview; - } - } - [Flags] enum AndroidToolchainComponentType { diff --git a/build-tools/xaprepare/xaprepare/Application/Context.cs b/build-tools/xaprepare/xaprepare/Application/Context.cs index d88afc51e21..d9b556c429c 100644 --- a/build-tools/xaprepare/xaprepare/Application/Context.cs +++ b/build-tools/xaprepare/xaprepare/Application/Context.cs @@ -282,11 +282,6 @@ public string DebugFileExtension { /// public RefreshableComponent ComponentsToRefresh { get; set; } - /// - /// Collection of Android SDK platform levels to be installed. - /// - public IEnumerable AndroidSdkPlatforms { get; set; } = Enumerable.Empty (); - /// /// Path to a local .NET SDK archive to use instead of downloading. /// diff --git a/build-tools/xaprepare/xaprepare/Main.cs b/build-tools/xaprepare/xaprepare/Main.cs index 8676b92d7a2..f3fc794f2b0 100644 --- a/build-tools/xaprepare/xaprepare/Main.cs +++ b/build-tools/xaprepare/xaprepare/Main.cs @@ -28,7 +28,6 @@ sealed class ParsedOptions public bool AutoProvision { get; set; } public bool AutoProvisionUsesSudo { get; set; } public RefreshableComponent RefreshList { get; set; } - public IEnumerable AndroidSdkPlatforms { get; set; } = new [] { "latest" }; public string? LocalDotNetSdkArchive { get; set; } } @@ -101,7 +100,6 @@ static async Task Run (string[] args) "", {"auto-provision=", $"Automatically install software required by .NET for Android", v => parsedOptions.AutoProvision = ParseBoolean (v)}, {"auto-provision-uses-sudo=", $"Allow use of sudo(1) when provisioning", v => parsedOptions.AutoProvisionUsesSudo = ParseBoolean (v)}, - {"android-sdk-platforms=", "Comma separated list of Android SDK platform levels to be installed or 'latest' or 'all'. Defaults to 'latest' if no value is provided.", v => parsedOptions.AndroidSdkPlatforms = ParseAndroidSdkPlatformLevels (v?.Trim () ?? String.Empty) }, {"dotnet-sdk-archive=", "Path to a local .NET SDK archive (zip or tar.gz) to use instead of downloading from the internet.", v => parsedOptions.LocalDotNetSdkArchive = v?.Trim () }, "", {"h|help", "Show this help message", v => parsedOptions.ShowHelp = true }, @@ -133,7 +131,6 @@ static async Task Run (string[] args) Context.Instance.AutoProvision = parsedOptions.AutoProvision; Context.Instance.AutoProvisionUsesSudo = parsedOptions.AutoProvisionUsesSudo; Context.Instance.ComponentsToRefresh = parsedOptions.RefreshList; - Context.Instance.AndroidSdkPlatforms = parsedOptions.AndroidSdkPlatforms; Context.Instance.LocalDotNetSdkArchive = parsedOptions.LocalDotNetSdkArchive; if (!String.IsNullOrEmpty (parsedOptions.Configuration)) @@ -327,18 +324,5 @@ RefreshableComponent ParseSingleComponent (string component) { } } - static IEnumerable ParseAndroidSdkPlatformLevels (string list) - { - // If the user specified "all" we return 'all' to indicate that all platforms should be installed. - if (string.Compare ("all", list, StringComparison.OrdinalIgnoreCase) == 0) - return new string [] { "all" }; - - // If the user did not specify anything, we return "latest" to indicate that only the latest platform should be installed. - if (string.IsNullOrEmpty (list) || string.Compare ("latest", list, StringComparison.OrdinalIgnoreCase) == 0) - return new string [] { "latest" }; - - // The user specified a list of platform levels to install, so we should respect that. - return list.Split (',').Select (item => item.Trim ()); - } } } diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs index 629269adff9..43ad7fa875f 100644 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs +++ b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using System.Xml.Linq; using Kajabity.Tools.Java; namespace Xamarin.Android.Prepare @@ -68,7 +67,6 @@ protected override async Task Execute (Context context) toolchain.Components.ForEach (c => Check (context, packageCacheDir, sdkRoot, c, toInstall, 4)); if (toInstall.Count == 0) { - WritePackageXmls (sdkRoot); return GatherNDKInfo (context); } @@ -108,8 +106,6 @@ protected override async Task Execute (Context context) await Unpack (context, tempDir, p); } - WritePackageXmls (sdkRoot); - return GatherNDKInfo (context); } @@ -287,12 +283,6 @@ void Check (Context context, string packageCacheDir, string sdkRoot, AndroidTool return; } - // If only specific Android SDK platforms were requested, ignore ones that were not requested - if (component is AndroidPlatformComponent apc && !ShouldInstall (apc, context)) { - LogStatus ($"skipping, not requested", padLeft, Log.InfoColor); - return; - } - if (missing) LogStatus (statusMissing, padLeft, ConsoleColor.Magenta); else @@ -310,22 +300,6 @@ void Check (Context context, string packageCacheDir, string sdkRoot, AndroidTool toInstall.Add (pkg); } - bool ShouldInstall (AndroidPlatformComponent component, Context context) - { - var platforms = context.AndroidSdkPlatforms; - - // If no specific platforms were requested, install everything - if (!platforms.Any () || platforms.Contains ("all")) - return true; - - // If "latest" was requested, install the highest available stable version and any preview versions - if (platforms.Contains ("latest") && (component.IsLatestStable || component.IsPreview)) - return true; - - // Check if this is a user-requested platform - return context.AndroidSdkPlatforms.Contains (component.ApiLevel); - } - bool IsInstalled (AndroidToolchainComponent component, string path, out bool missing) { missing = true; @@ -389,60 +363,5 @@ bool IsNdk (AndroidToolchainComponent component) { return component.Name.StartsWith ("android-ndk", StringComparison.OrdinalIgnoreCase); } - - static readonly XNamespace AndroidRepositoryCommon = "http://schemas.android.com/repository/android/common/01"; - static readonly XNamespace AndroidRepositoryGeneric = "http://schemas.android.com/repository/android/generic/01"; - - void WritePackageXmls (string sdkRoot) - { - string[] packageXmlDirs = new[]{ - Path.Combine (sdkRoot, "emulator"), - }; - foreach (var path in packageXmlDirs) { - var properties = ReadSourceProperties (path); - if (properties == null) - continue; - string packageXml = Path.Combine (path, "package.xml"); - Log.DebugLine ($"Writing '{packageXml}'"); - var doc = new XDocument( - new XElement (AndroidRepositoryCommon + "repository", - new XAttribute (XNamespace.Xmlns + "ns2", AndroidRepositoryCommon.NamespaceName), - new XAttribute (XNamespace.Xmlns + "ns3", AndroidRepositoryGeneric.NamespaceName), - new XElement ("localPackage", - new XAttribute ("path", properties ["Pkg.Path"]), - new XAttribute ("obsolete", "false"), - new XElement ("revision", GetRevision (properties ["Pkg.Revision"])), - new XElement ("display-name", properties ["Pkg.Desc"])))); - doc.Save (packageXml, SaveOptions.None); - } - } - - Dictionary? ReadSourceProperties (string dir) - { - var path = Path.Combine (dir, "source.properties"); - if (!File.Exists (path)) - return null; - var dict = new Dictionary (); - foreach (var line in File.ReadLines (path)) { - if (line.Length == 0) - continue; - var entry = line.Split (new[]{'='}, 2, StringSplitOptions.None); - if (entry.Length != 2) - continue; - dict.Add (entry [0], entry [1]); - } - return dict; - } - - IEnumerable GetRevision (string revision) - { - var parts = revision.Split ('.'); - if (parts.Length > 0) - yield return new XElement ("major", parts [0]); - if (parts.Length > 1) - yield return new XElement ("minor", parts [1]); - if (parts.Length > 2) - yield return new XElement ("micro", parts [2]); - } } } From bc7ba460a3806bed1120943bb1d29131199305ad Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 11:37:54 -0500 Subject: [PATCH 05/13] Migrate NDK download/extract from xaprepare to MSBuild Adds the Android NDK as another _AndroidSdkPackage row in src/androidsdk/androidsdk.targets and ports Step_Android_SDK_NDK.CopyRedistributableFiles to a new CopyNdkRedistributables BootstrapTask. NDK version info (BuildInfo.NDKRevision, etc.) is now computed from the BuildAndroidPlatforms.AndroidNdkPkgRevision constant instead of reading the NDK's source.properties at runtime. Deletes (now unused) from xaprepare: - `Step_Android_SDK_NDK.cs` - `AndroidToolchain.cs` + `.Linux`/`.MacOS`/`.Windows` partials - `AndroidToolchainComponent.cs` - `RefreshableComponent.cs` - `BuildInfo.GatherNDKInfo`, `NDKVersionTag`, `NDKMinimumApiAvailable` - `Configurables.Defaults.{AndroidToolchainPrefixes,AbiToRID,AbiToClangArch}` - `Configurables.Paths.{AndroidToolchainRootDirectory,AndroidClangRootDirectory,AndroidToolchainBinDirectory,AndroidToolchainSysrootLibDirectory}` - `Configurables.Paths.NdkToolchainOSTag` (per-OS partials) - `Context.ComponentsToRefresh` - `Main.ParseRefreshableComponents`, `--refresh` CLI flag - `Scenario_*.AndroidSdkNdkType` Net: ~66 added, ~672 removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Configuration.props | 7 + .../CopyNdkRedistributables.cs | 117 ++++++ .../Application/AndroidToolchainComponent.cs | 53 --- .../xaprepare/Application/BuildInfo.cs | 91 +---- .../xaprepare/Application/Context.cs | 5 - .../Application/RefreshableComponent.cs | 12 - .../ConfigAndData/Configurables.Linux.cs | 1 - .../ConfigAndData/Configurables.MacOS.cs | 1 - .../ConfigAndData/Configurables.Windows.cs | 1 - .../xaprepare/ConfigAndData/Configurables.cs | 38 -- .../Dependencies/AndroidToolchain.Linux.cs | 11 - .../Dependencies/AndroidToolchain.MacOS.cs | 7 - .../Dependencies/AndroidToolchain.Windows.cs | 7 - .../Dependencies/AndroidToolchain.cs | 56 --- build-tools/xaprepare/xaprepare/Main.cs | 33 -- .../Scenario_AndroidTestDependencies.cs | 3 - .../Scenario_EmulatorTestDependencies.cs | 2 - .../xaprepare/Scenarios/Scenario_Standard.cs | 1 - .../xaprepare/Steps/Step_Android_SDK_NDK.cs | 367 ------------------ src/androidsdk/androidsdk.targets | 42 +- 20 files changed, 183 insertions(+), 672 deletions(-) create mode 100644 build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CopyNdkRedistributables.cs delete mode 100644 build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs delete mode 100644 build-tools/xaprepare/xaprepare/Application/RefreshableComponent.cs delete mode 100644 build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs delete mode 100644 build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs delete mode 100644 build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs delete mode 100644 build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs delete mode 100644 build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs diff --git a/Configuration.props b/Configuration.props index 14226e83bb9..faac18fa377 100644 --- a/Configuration.props +++ b/Configuration.props @@ -135,6 +135,13 @@ A3F91808DCE50C1717737DE90C18479ED3A78B147E06985247D138E7AB5123D0 68DB2690CB92E4EE5373AC9B792642C90717D8F417D83ECCEA48781171B3182A BD97900346A70C784AC8B15C809539DDEF34ED3B5BE1DF8A9A89CF298BE93798 + + <_XAAndroidNdkRelease>28c + <_XAAndroidNdkPkgRevision>28.2.13676358 + 0D4599E8BBF1A1668A0D51A541729B2246360F350018A2081D0B302DBB594F2A + DFB20D396DF28CA02A8C708314B814A4D961DC9074F9A161932746F815AA552F + 6BEC98AC2354D8A919760889A1A41D020132E5E8CFA1B1FE51610A72C36A466B 1.18.3 A099CFA1543F55593BC2ED16A70A7C67FE54B1747BB7301F37FDFD6D91028E29 L_18.1.6-8.0.0-1 diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CopyNdkRedistributables.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CopyNdkRedistributables.cs new file mode 100644 index 00000000000..5ca2bbf2ecf --- /dev/null +++ b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CopyNdkRedistributables.cs @@ -0,0 +1,117 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Xamarin.Android.Tools.BootstrapTasks +{ + // Copies the NDK redistributable files (CRT, libc++, libunwind, libclang_rt.builtins) + // for every supported ABI from the extracted NDK into the runtime redist directory. + // MSBuild port of xaprepare's `Step_Android_SDK_NDK.CopyRedistributableFiles ()`. + public class CopyNdkRedistributables : Task + { + static readonly string [] CRTFiles = { + "crtbegin_so.o", + "crtend_so.o", + "libc.so", + "libdl.so", + "liblog.so", + "libm.so", + "libz.so", + }; + + static readonly string [] CPPAbiFiles = { + "libc++_static.a", + "libc++abi.a", + }; + + static readonly string [] ClangArchFiles = { + "libunwind.a", + }; + + static readonly (string Abi, string ToolchainPrefix, string ClangArch, string Rid) [] Abis = { + ("armeabi-v7a", "arm-linux-androideabi", "arm", "android-arm"), + ("arm64-v8a", "aarch64-linux-android", "aarch64", "android-arm64"), + ("x86", "i686-linux-android", "i686", "android-x86"), + ("x86_64", "x86_64-linux-android", "x86_64", "android-x64"), + }; + + [Required] + public string AndroidNdkDirectory { get; set; } = ""; + + [Required] + public string NdkToolchainOSTag { get; set; } = ""; + + [Required] + public string MinimumApiLevel { get; set; } = ""; + + [Required] + public string OutputDirectory { get; set; } = ""; + + public override bool Execute () + { + string toolchainRoot = Path.Combine (AndroidNdkDirectory, "toolchains", "llvm", "prebuilt", NdkToolchainOSTag); + string sysrootLib = Path.Combine (toolchainRoot, "sysroot", "usr", "lib"); + string clangRoot = Path.Combine (toolchainRoot, "lib", "clang"); + string androidVer = Path.Combine (toolchainRoot, "AndroidVersion.txt"); + + if (!File.Exists (androidVer)) { + Log.LogError ($"Android NDK version file not found: '{androidVer}'"); + return false; + } + + string [] lines = File.ReadAllLines (androidVer); + if (lines.Length < 1) { + Log.LogError ($"Unknown format of Android NDK version file '{androidVer}'"); + return false; + } + + string [] llvmVersion = lines [0].Split ('.'); + if (llvmVersion.Length < 3) { + Log.LogError ($"Unknown LLVM version format for '{lines [0]}'"); + return false; + } + + string clangLibPath = Path.Combine (clangRoot, llvmVersion [0], "lib", "linux"); + + foreach (var (abi, toolchainPrefix, clangArch, rid) in Abis) { + string abiDir = Path.Combine (sysrootLib, toolchainPrefix); + string crtFilesPath = Path.Combine (abiDir, MinimumApiLevel); + string outputDir = Path.Combine (OutputDirectory, rid); + + Directory.CreateDirectory (outputDir); + + foreach (string file in CRTFiles) + CopyFile (abi, crtFilesPath, file, outputDir); + + foreach (string file in CPPAbiFiles) + CopyFile (abi, abiDir, file, outputDir); + + CopyFile (abi, clangLibPath, $"libclang_rt.builtins-{clangArch}-android.a", outputDir); + + // Yay, consistency + string archDir = string.Equals (clangArch, "i686", StringComparison.Ordinal) ? "i386" : clangArch; + string clangArchLibPath = Path.Combine (clangLibPath, archDir); + + foreach (string file in ClangArchFiles) + CopyFile (abi, clangArchLibPath, file, outputDir); + } + + return !Log.HasLoggedErrors; + } + + void CopyFile (string abi, string sourceDir, string fileName, string outputDir) + { + string sourceFile = Path.Combine (sourceDir, fileName); + string destFile = Path.Combine (outputDir, fileName); + if (!File.Exists (sourceFile)) { + Log.LogError ($"NDK redistributable not found for {abi}: '{sourceFile}'"); + return; + } + Log.LogMessage (MessageImportance.Low, $" Copying NDK redistributable: {fileName} ({abi})"); + File.Copy (sourceFile, destFile, overwrite: true); + } + } +} diff --git a/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs b/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs deleted file mode 100644 index 787a6bd0f2c..00000000000 --- a/build-tools/xaprepare/xaprepare/Application/AndroidToolchainComponent.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; - -namespace Xamarin.Android.Prepare -{ - class AndroidToolchainComponent : AppObject, IBuildInventoryItem - { - public string Name { get; } - public string DestDir { get; } - public Uri? RelativeUrl { get; } - public bool IsMultiVersion { get; } - public bool NoSubdirectory { get; } - public string? PkgRevision { get; } - public AndroidToolchainComponentType DependencyType { get; } - public string BuildToolName { get; } - public string BuildToolVersion { get; } - - public AndroidToolchainComponent (string name, string destDir, Uri? relativeUrl = null, bool isMultiVersion = false, bool noSubdirectory = false, string? pkgRevision = null, - AndroidToolchainComponentType dependencyType = AndroidToolchainComponentType.CoreDependency, string buildToolName = "", string buildToolVersion = "") - { - if (String.IsNullOrEmpty (name)) - throw new ArgumentException ("must not be null or empty", nameof (name)); - if (String.IsNullOrEmpty (destDir)) - throw new ArgumentException ("must not be null or empty", nameof (destDir)); - - Name = name; - DestDir = destDir; - RelativeUrl = relativeUrl; - IsMultiVersion = isMultiVersion; - NoSubdirectory = noSubdirectory; - PkgRevision = pkgRevision; - DependencyType = dependencyType; - BuildToolName = string.IsNullOrEmpty (buildToolName) ? $"android-sdk-{name}" : buildToolName; - BuildToolVersion = buildToolVersion; - } - - public void AddToInventory () - { - if (!string.IsNullOrEmpty (BuildToolName) && !string.IsNullOrEmpty (BuildToolVersion) && !Context.Instance.BuildToolsInventory.ContainsKey (BuildToolName)) { - Context.Instance.BuildToolsInventory.Add (BuildToolName, BuildToolVersion); - } - } - } - - [Flags] - enum AndroidToolchainComponentType - { - CoreDependency = 0, - BuildDependency = 1 << 0, - EmulatorDependency = 1 << 1, - All = CoreDependency | BuildDependency | EmulatorDependency, - } - -} diff --git a/build-tools/xaprepare/xaprepare/Application/BuildInfo.cs b/build-tools/xaprepare/xaprepare/Application/BuildInfo.cs index ec2a39f9b82..5674d987d41 100644 --- a/build-tools/xaprepare/xaprepare/Application/BuildInfo.cs +++ b/build-tools/xaprepare/xaprepare/Application/BuildInfo.cs @@ -9,17 +9,26 @@ namespace Xamarin.Android.Prepare { partial class BuildInfo : AppObject { - static readonly char[] NDKPropertySeparator = new [] { '=' }; - public string CommitOfLastVersionChange { get; private set; } = String.Empty; - // Not available from the start, only after NDK is installed - public string NDKRevision { get; private set; } = String.Empty; - public string NDKVersionMajor { get; private set; } = String.Empty; - public string NDKVersionMinor { get; private set; } = String.Empty; - public string NDKVersionMicro { get; private set; } = String.Empty; - public string NDKVersionTag { get; private set; } = String.Empty; - public string NDKMinimumApiAvailable { get; private set; } = String.Empty; + // NDK version info is now derived directly from BuildAndroidPlatforms.AndroidNdkPkgRevision + // (single source of truth shared with src/androidsdk/androidsdk.targets via Configuration.props). + public string NDKRevision => BuildAndroidPlatforms.AndroidNdkPkgRevision; + public string NDKVersionMajor => NDKVersion.Major.ToString (); + public string NDKVersionMinor => NDKVersion.Minor.ToString (); + public string NDKVersionMicro => NDKVersion.Build.ToString (); + + Version? cachedNdkVersion; + Version NDKVersion { + get { + if (cachedNdkVersion != null) + return cachedNdkVersion; + if (!Utilities.ParseAndroidPkgRevision (BuildAndroidPlatforms.AndroidNdkPkgRevision, out Version? ver, out _) || ver == null) + throw new InvalidOperationException ($"Unable to parse NDK revision '{BuildAndroidPlatforms.AndroidNdkPkgRevision}' as a valid version string"); + cachedNdkVersion = ver; + return ver; + } + } public string VersionHash { get; private set; } = String.Empty; public string XACommitHash { get; private set; } = String.Empty; @@ -44,70 +53,6 @@ void DetermineXACommitInfo (Context context) XABranch = git.GetBranchName (); } - public bool GatherNDKInfo (Context context) - { - string ndkDir = Configurables.Paths.AndroidNdkDirectory; - string props = Path.Combine (ndkDir, "source.properties"); - if (!File.Exists (props)) { - Log.ErrorLine ("NDK properties file does not exist: ", props, tailColor: Log.DestinationColor); - return false; - } - - string[] lines = File.ReadAllLines (props); - foreach (string l in lines) { - string line = l.Trim (); - string[] parts = line.Split (NDKPropertySeparator, 2); - if (parts.Length != 2) - continue; - - if (String.Compare ("Pkg.Revision", parts [0].Trim (), StringComparison.Ordinal) != 0) - continue; - - string rev = parts [1].Trim (); - NDKRevision = rev; - - if (!Utilities.ParseAndroidPkgRevision (rev, out Version? ver, out string? tag) || ver == null) { - Log.ErrorLine ($"Unable to parse NDK revision '{rev}' as a valid version string"); - return false; - } - - NDKVersionMajor = ver.Major.ToString (); - NDKVersionMinor = ver.Minor.ToString (); - NDKVersionMicro = ver.Build.ToString (); - NDKVersionTag = tag ?? String.Empty; - break; - } - - Log.DebugLine ($"Looking for minimum API available in {ndkDir}"); - int minimumApi = Int32.MaxValue; - foreach (var kvp in Configurables.Defaults.AndroidToolchainPrefixes) { - string dirName = kvp.Value; - string platforms = Path.Combine (Configurables.Paths.AndroidToolchainSysrootLibDirectory, dirName); - Log.DebugLine ($" searching in {platforms}"); - foreach (string p in Directory.EnumerateDirectories (platforms, "*", SearchOption.TopDirectoryOnly)) { - string plibc = Path.Combine (p, "libc.so"); - if (!Utilities.FileExists (plibc)) { - continue; - } - - Log.DebugLine ($" found {p}"); - string pdir = Path.GetFileName (p); - int api; - if (!Int32.TryParse (pdir, out api)) - continue; - - if (api >= minimumApi) - continue; - - minimumApi = api; - } - } - - Log.DebugLine ($"Detected minimum NDK API level: {minimumApi}"); - NDKMinimumApiAvailable = minimumApi.ToString (); - return true; - } - async Task DetermineLastVersionChangeCommit (Context context) { Log.StatusLine ($" {context.Characters.Bullet} Commit of last version change", ConsoleColor.Gray); diff --git a/build-tools/xaprepare/xaprepare/Application/Context.cs b/build-tools/xaprepare/xaprepare/Application/Context.cs index d9b556c429c..d56517a0ffb 100644 --- a/build-tools/xaprepare/xaprepare/Application/Context.cs +++ b/build-tools/xaprepare/xaprepare/Application/Context.cs @@ -277,11 +277,6 @@ public string DebugFileExtension { } } - /// - /// Collection of programs or dependencies which should be reinstalled. - /// - public RefreshableComponent ComponentsToRefresh { get; set; } - /// /// Path to a local .NET SDK archive to use instead of downloading. /// diff --git a/build-tools/xaprepare/xaprepare/Application/RefreshableComponent.cs b/build-tools/xaprepare/xaprepare/Application/RefreshableComponent.cs deleted file mode 100644 index cda3299dbe9..00000000000 --- a/build-tools/xaprepare/xaprepare/Application/RefreshableComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -namespace Xamarin.Android.Prepare -{ - [Flags] - public enum RefreshableComponent - { - None = 0, - AndroidSDK = 1, - AndroidNDK = 2, - All = AndroidSDK | AndroidNDK - } -} diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs index be98ee37cb6..87be9519266 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Linux.cs @@ -11,7 +11,6 @@ partial class Defaults partial class Paths { - public const string NdkToolchainOSTag = "linux-x86_64"; } } } diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs index 3ea71d15b40..0503c1cb566 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.MacOS.cs @@ -12,7 +12,6 @@ partial class Defaults partial class Paths { public const string MonoCrossRuntimeInstallPath = "Darwin"; - public const string NdkToolchainOSTag = "darwin-x86_64"; } } } diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs index e4b80c85147..7c7a025398f 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.Windows.cs @@ -12,7 +12,6 @@ partial class Defaults partial class Paths { - public const string NdkToolchainOSTag = "windows-x86_64"; } partial class Urls diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs index a48f23b95f7..a85850935bc 100644 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs +++ b/build-tools/xaprepare/xaprepare/ConfigAndData/Configurables.cs @@ -126,27 +126,6 @@ public static partial class Defaults /// public const string HashAlgorithm = "SHA1"; - public static readonly Dictionary AndroidToolchainPrefixes = new Dictionary (StringComparer.Ordinal) { - { "armeabi-v7a", "arm-linux-androideabi" }, - { "arm64-v8a", "aarch64-linux-android" }, - { "x86", "i686-linux-android" }, - { "x86_64", "x86_64-linux-android" }, - }; - - public static readonly Dictionary AbiToRID = new (StringComparer.Ordinal) { - { "armeabi-v7a", "android-arm" }, - { "arm64-v8a", "android-arm64" }, - { "x86", "android-x86" }, - { "x86_64", "android-x64" }, - }; - - public static readonly Dictionary AbiToClangArch = new (StringComparer.Ordinal) { - { "armeabi-v7a", "arm" }, - { "arm64-v8a", "aarch64" }, - { "x86", "i686" }, - { "x86_64", "x86_64" }, - }; - /// /// ABIs that support the NativeAOT runtime. Used to determine which ABIs /// need the higher API-level CRT/sysroot files in the NativeAOT runtime pack. @@ -232,10 +211,6 @@ public static partial class Paths // Other public static string AndroidNdkDirectory => ctx.Properties.GetRequiredValue (KnownProperties.AndroidNdkDirectory); - public static string AndroidToolchainRootDirectory => GetCachedPath (ref androidToolchainRootDirectory, () => Path.Combine (AndroidNdkDirectory, "toolchains", "llvm", "prebuilt", NdkToolchainOSTag)); - public static string AndroidClangRootDirectory => GetCachedPath (ref androidClangRootDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "lib", "clang")); - public static string AndroidToolchainBinDirectory => GetCachedPath (ref androidToolchainBinDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "bin")); - public static string AndroidToolchainSysrootLibDirectory => GetCachedPath (ref androidToolchainSysrootLibDirectory, () => Path.Combine (AndroidToolchainRootDirectory, "sysroot", "usr", "lib")); public static string AndroidBuildToolsCacheDir => ctx.Properties.GetRequiredValue (KnownProperties.AndroidToolchainCacheDirectory); // not really configurables, merely convenience aliases for more frequently used paths that come from properties @@ -264,15 +239,6 @@ static string GetCoreClrAppRuntimePath (Context ctx, string androidTarget) ); } - static string EnsureAndroidToolchainBinDirectories () - { - if (androidToolchainBinDirectory != null) - return androidToolchainBinDirectory; - - androidToolchainBinDirectory = Path.Combine (ctx.Properties.GetRequiredValue (KnownProperties.AndroidNdkDirectory), "toolchains", "llvm", "prebuilt", NdkToolchainOSTag, "bin"); - return androidToolchainBinDirectory; - } - static string GetCachedPath (ref string? variable, Func creator) { if (!String.IsNullOrEmpty (variable)) @@ -285,10 +251,6 @@ static string GetCachedPath (ref string? variable, Func creator) static string? testBinDir; static string? buildBinDir; static string? binDir; - static string? androidToolchainRootDirectory; - static string? androidClangRootDirectory; - static string? androidToolchainBinDirectory; - static string? androidToolchainSysrootLibDirectory; static string? installMSBuildDir; static string? monoAndroidFrameworksRootDir; static string? externalJavaInteropDir; diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs deleted file mode 100644 index e540a5252e1..00000000000 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Linux.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Prepare -{ - partial class AndroidToolchain - { - static readonly string osTag = "linux"; - } -} diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs deleted file mode 100644 index 669b0b750b6..00000000000 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.MacOS.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Xamarin.Android.Prepare -{ - partial class AndroidToolchain - { - static readonly string osTag = "darwin"; - } -} diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs deleted file mode 100644 index b3383b1684e..00000000000 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.Windows.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Xamarin.Android.Prepare -{ - partial class AndroidToolchain - { - static readonly string osTag = "windows"; - } -} diff --git a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs b/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs deleted file mode 100644 index d1937e1427e..00000000000 --- a/build-tools/xaprepare/xaprepare/ConfigAndData/Dependencies/AndroidToolchain.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Xamarin.Android.Prepare -{ - partial class AndroidToolchain : AppObject - { - public static readonly Uri AndroidUri = Configurables.Urls.AndroidToolchain_AndroidUri; - - public List Components { get; } - - public AndroidToolchain () - { - string AndroidNdkVersion = BuildAndroidPlatforms.AndroidNdkVersion; - string AndroidPkgRevision = BuildAndroidPlatforms.AndroidNdkPkgRevision; - string AndroidNdkDirectory = GetRequiredProperty (KnownProperties.AndroidNdkDirectory); - - // Upstream manifests with version information: - // - // https://dl-ssl.google.com/android/repository/repository2-1.xml - // https://dl-ssl.google.com/android/repository/repository2-3.xml - // * platform APIs - // * build-tools - // * command-line tools - // * sdk-tools - // * platform-tools - // - // https://dl-ssl.google.com/android/repository/addon2-1.xml - // * android_m2repository_r47 - // - // https://dl-ssl.google.com/android/repository/sys-img/android/sys-img2-1.xml - // https://dl-ssl.google.com/android/repository/sys-img/google_apis/sys-img2-1.xml - // * system images - // - // Everything that lives under $(AndroidSdkDirectory) is downloaded by - // `src/androidsdk/androidsdk.targets`. Only the NDK remains here. - Components = new List { - new AndroidToolchainComponent ($"android-ndk-r{AndroidNdkVersion}-{osTag}", - destDir: AndroidNdkDirectory, - pkgRevision: AndroidPkgRevision, - buildToolName: $"android-ndk-r{AndroidNdkVersion}", - buildToolVersion: AndroidPkgRevision - ), - }; - } - - static string GetRequiredProperty (string propertyName) - { - string? value = Context.Instance.Properties [propertyName]; - if (String.IsNullOrEmpty (value)) - throw new InvalidOperationException ($"Required property '{propertyName}' not defined"); - return value!; - } - } -} diff --git a/build-tools/xaprepare/xaprepare/Main.cs b/build-tools/xaprepare/xaprepare/Main.cs index f3fc794f2b0..d9157457413 100644 --- a/build-tools/xaprepare/xaprepare/Main.cs +++ b/build-tools/xaprepare/xaprepare/Main.cs @@ -27,7 +27,6 @@ sealed class ParsedOptions public string? Configuration { get; set; } public bool AutoProvision { get; set; } public bool AutoProvisionUsesSudo { get; set; } - public RefreshableComponent RefreshList { get; set; } public string? LocalDotNetSdkArchive { get; set; } } @@ -96,7 +95,6 @@ static async Task Run (string[] args) {"ls", "List names of all known scenarios", v => parsedOptions.ListScenarios = true }, {"cf=", $"{{NAME}} of the compression format to use for some archives (e.g. the XA bundle). One of: {GetCompressionFormatNames ()}; Default: {parsedOptions.CompressionFormat}", v => parsedOptions.CompressionFormat = v?.Trim () ?? String.Empty}, {"c|configuration=", $"Build {{CONFIGURATION}}. Default: {Context.Instance.Configuration}", v => parsedOptions.Configuration = v?.Trim ()}, - {"refresh:", "[sdk,ndk] Comma separated list of components which should be reinstalled. Defaults to all supported components if no value is provided.", v => parsedOptions.RefreshList = ParseRefreshableComponents (v?.Trim () ?? String.Empty)}, "", {"auto-provision=", $"Automatically install software required by .NET for Android", v => parsedOptions.AutoProvision = ParseBoolean (v)}, {"auto-provision-uses-sudo=", $"Allow use of sudo(1) when provisioning", v => parsedOptions.AutoProvisionUsesSudo = ParseBoolean (v)}, @@ -130,7 +128,6 @@ static async Task Run (string[] args) Context.Instance.DebugFileExtension = parsedOptions.DebugFileExtension; Context.Instance.AutoProvision = parsedOptions.AutoProvision; Context.Instance.AutoProvisionUsesSudo = parsedOptions.AutoProvisionUsesSudo; - Context.Instance.ComponentsToRefresh = parsedOptions.RefreshList; Context.Instance.LocalDotNetSdkArchive = parsedOptions.LocalDotNetSdkArchive; if (!String.IsNullOrEmpty (parsedOptions.Configuration)) @@ -294,35 +291,5 @@ static bool ParseBoolean (string? value) throw new InvalidOperationException ($"Unknown boolean value: {value}"); } - static RefreshableComponent ParseRefreshableComponents (string refreshList) - { - if (String.IsNullOrEmpty (refreshList)) - return RefreshableComponent.All; - - if (refreshList.IndexOf (',') == -1) - return ParseSingleComponent (refreshList); - - var allParsedComponents = RefreshableComponent.None; - var refreshListArray = refreshList.Split (','); - foreach (var c in refreshListArray) { - RefreshableComponent parsed = ParseSingleComponent (c); - if (parsed != RefreshableComponent.None) - allParsedComponents |= parsed; - } - - return allParsedComponents; - - - RefreshableComponent ParseSingleComponent (string component) { - if (String.Compare ("sdk", component, StringComparison.OrdinalIgnoreCase) == 0) - return RefreshableComponent.AndroidSDK; - - if (String.Compare ("ndk", component, StringComparison.OrdinalIgnoreCase) == 0) - return RefreshableComponent.AndroidNDK; - - return RefreshableComponent.None; - } - } - } } diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs index d3f58521063..ca0c311f2d2 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_AndroidTestDependencies.cs @@ -5,8 +5,6 @@ namespace Xamarin.Android.Prepare [Scenario (isDefault: false)] partial class Scenario_AndroidTestDependencies : ScenarioNoStandardEndSteps { - protected virtual AndroidToolchainComponentType AndroidSdkNdkType => AndroidToolchainComponentType.CoreDependency; - public Scenario_AndroidTestDependencies () : base ("AndroidTestDependencies", "Install Android SDK and .NET preview test dependencies.") {} @@ -18,7 +16,6 @@ protected Scenario_AndroidTestDependencies (string name, string description) protected override void AddSteps (Context context) { Steps.Add (new Step_InstallDotNetPreview ()); - Steps.Add (new Step_Android_SDK_NDK (AndroidSdkNdkType)); // disable installation of missing programs... context.SetCondition (KnownConditions.AllowProgramInstallation, false); diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs index 5370f0c6364..9aeababa4a6 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_EmulatorTestDependencies.cs @@ -5,8 +5,6 @@ namespace Xamarin.Android.Prepare [Scenario (isDefault: false)] partial class Scenario_EmulatorTestDependencies : Scenario_AndroidTestDependencies { - protected override AndroidToolchainComponentType AndroidSdkNdkType => AndroidToolchainComponentType.CoreDependency | AndroidToolchainComponentType.EmulatorDependency; - public Scenario_EmulatorTestDependencies () : base ("EmulatorTestDependencies", "Install Android SDK (with emulator) and .NET preview test dependencies.") {} diff --git a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs index b2b701f82db..560994a1505 100644 --- a/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs +++ b/build-tools/xaprepare/xaprepare/Scenarios/Scenario_Standard.cs @@ -19,7 +19,6 @@ protected override void AddSteps (Context context) throw new ArgumentNullException (nameof (context)); Steps.Add (new Step_InstallDotNetPreview ()); - Steps.Add (new Step_Android_SDK_NDK ()); Steps.Add (new Step_GenerateFiles (atBuildStart: true)); Steps.Add (new Step_PrepareProps ()); Steps.Add (new Step_GenerateCGManifest ()); diff --git a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs deleted file mode 100644 index 43ad7fa875f..00000000000 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs +++ /dev/null @@ -1,367 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Kajabity.Tools.Java; - -namespace Xamarin.Android.Prepare -{ - class Step_Android_SDK_NDK : StepWithDownloadProgress - { -#nullable disable - sealed class AndroidPackage - { - public AndroidToolchainComponent Component; - public string PackageName; - public Uri Url; - public string LocalPackagePath; - public string DestinationDir; - } -#nullable enable - - static readonly string[] CRTFiles = { - "crtbegin_so.o", - "crtend_so.o", - "libc.so", - "libdl.so", - "liblog.so", - "libm.so", - "libz.so", - }; - - static readonly string[] CPPAbiFiles = { - "libc++_static.a", - "libc++abi.a", - }; - - static readonly string[] ClangArchFiles = { - "libunwind.a", - }; - - bool RefreshSdk = false; - bool RefreshNdk = false; - AndroidToolchainComponentType DependencyTypeToInstall = AndroidToolchainComponentType.All; - - public Step_Android_SDK_NDK (AndroidToolchainComponentType dependencyTypeToInstall = AndroidToolchainComponentType.All) - : base ("Preparing Android SDK and NDK") - { - DependencyTypeToInstall = dependencyTypeToInstall; - } - - protected override async Task Execute (Context context) - { - string sdkRoot = context.Properties.GetRequiredValue (KnownProperties.AndroidSdkDirectory); - string ndkRoot = context.Properties.GetRequiredValue (KnownProperties.AndroidNdkDirectory); - string packageCacheDir = context.Properties.GetRequiredValue (KnownProperties.AndroidToolchainCacheDirectory); - - RefreshSdk = context.ComponentsToRefresh.HasFlag (RefreshableComponent.AndroidSDK); - RefreshNdk = context.ComponentsToRefresh.HasFlag (RefreshableComponent.AndroidNDK); - - Log.StatusLine ("Android SDK location: ", sdkRoot, tailColor: Log.DestinationColor); - Log.StatusLine ("Android NDK location: ", ndkRoot, tailColor: Log.DestinationColor); - Log.DebugLine ($"Toolchain cache directory: {packageCacheDir}"); - - var toolchain = new AndroidToolchain (); - var toInstall = new List (); - - toolchain.Components.ForEach (c => Check (context, packageCacheDir, sdkRoot, c, toInstall, 4)); - if (toInstall.Count == 0) { - return GatherNDKInfo (context); - } - - Log.MessageLine (); - toInstall.ForEach (p => Log.DebugLine ($"Missing Android component: {p.Component.Name}")); - - string tempDir = Path.Combine (context.Properties.GetRequiredValue (KnownProperties.AndroidToolchainDirectory), "temp"); - Log.DebugLine ($"Toolchain temporary directory: {tempDir}"); - - if (Directory.Exists (tempDir)) { - Log.DebugLine ("Temporary directory exists, cleaning it up"); - Utilities.DeleteDirectorySilent (tempDir); - } - Directory.CreateDirectory (tempDir); - - Log.MessageLine ("Installing missing components"); - var toDownload = new List (); - toInstall.ForEach (c => CheckPackageStatus (context, packageCacheDir, c, toDownload)); - - if (toDownload.Count > 0) { - ulong totalDownloadSize = 0; - foreach (AndroidPackage pkg in toDownload) { - Log.DebugLine ($"Android component '{pkg.Component.Name}' will be downloaded from {pkg.Url}"); - (bool success, ulong size) = await Utilities.GetDownloadSize (pkg.Url); - if (!success) - continue; - totalDownloadSize += size; - } - - toDownload.ForEach (p => Log.StatusLine ($" {context.Characters.Link} {p.Url}", ConsoleColor.White)); - - DownloadStatus downloadStatus = Utilities.SetupDownloadStatus (context, totalDownloadSize, context.InteractiveSession); - await Task.WhenAll (toDownload.Select (p => Download (context, p.Url, p.LocalPackagePath, p.Component.Name, p.PackageName, downloadStatus))); - } - - foreach (AndroidPackage p in toInstall) { - await Unpack (context, tempDir, p); - } - - return GatherNDKInfo (context); - } - - bool GatherNDKInfo (Context context) - { - if (!CopyRedistributableFiles (context)) { - return false; - } - - // Always install the NDK, as NativeAOT-based tests require it - return context.BuildInfo.GatherNDKInfo (context); - } - - bool CopyRedistributableFiles (Context context) - { - string androidVersionPath = Path.Combine (Configurables.Paths.AndroidToolchainRootDirectory, "AndroidVersion.txt"); - if (!File.Exists (androidVersionPath)) { - throw new InvalidOperationException ($"Android version file '{androidVersionPath}' not found"); - } - - string[]? lines = File.ReadAllLines (androidVersionPath); - if (lines == null || lines.Length < 1) { - throw new InvalidOperationException ($"Unknown format of Android version file '{androidVersionPath}'"); - } - - // First line is (should be) the LLVM version, we need just the main release number - string[] llvmVersion = lines[0].Split ('.'); - if (llvmVersion.Length < 3) { - throw new InvalidOperationException ($"Unknown LLVM version format for '{lines[0]}'"); - } - - string clangLibPath = Path.Combine ( - Configurables.Paths.AndroidClangRootDirectory, - llvmVersion[0], - "lib", - "linux" - ); - - foreach (var kvp in Configurables.Defaults.AndroidToolchainPrefixes) { - string abi = kvp.Key; - string abiDir = Path.Combine (Configurables.Paths.AndroidToolchainSysrootLibDirectory, kvp.Value); - string crtFilesPath = Path.Combine (abiDir, BuildAndroidPlatforms.NdkMinimumAPI); - string clangArch = Configurables.Defaults.AbiToClangArch[abi]; - - foreach (string file in CRTFiles) { - CopyFile (abi, crtFilesPath, file); - } - - foreach (string file in CPPAbiFiles) { - CopyFile (abi, abiDir, file); - } - - CopyFile (abi, clangLibPath, $"libclang_rt.builtins-{clangArch}-android.a"); - - // Yay, consistency - if (String.Compare (clangArch, "i686", StringComparison.Ordinal) == 0) { - clangArch = "i386"; - } - string clangArchLibPath = Path.Combine (clangLibPath, clangArch); - - foreach (string file in ClangArchFiles) { - CopyFile (abi, clangArchLibPath, file); - } - } - - return true; - - void CopyFile (string abi, string sourceDir, string fileName) - { - Log.StatusLine ($" {context.Characters.Bullet} Copying NDK redistributable: ", $"{fileName} ({abi})", tailColor: ConsoleColor.White); - string rid = Configurables.Defaults.AbiToRID [abi]; - string outputDir = Path.Combine ( - context.Properties.GetRequiredValue (KnownProperties.NativeRuntimeOutputRootDir), - context.Properties.GetRequiredValue (KnownProperties.RuntimeRedistDirName), - rid - ); - - string sourceFile = Path.Combine (sourceDir, fileName); - Utilities.CopyFileToDir (sourceFile, outputDir); - } - } - - void CheckPackageStatus (Context context, string packageCacheDir, AndroidPackage pkg, List toDownload) - { - Log.StatusLine ($" {context.Characters.Bullet} Installing ", pkg.Component.Name, tailColor: ConsoleColor.White); - - if (File.Exists (pkg.LocalPackagePath)) { - if ((RefreshSdk && !IsNdk (pkg.Component)) || (RefreshNdk && IsNdk (pkg.Component))) { - LogStatus ("Reinstall requested, deleting cache", 4, ConsoleColor.Magenta); - Utilities.DeleteFile (pkg.LocalPackagePath); - toDownload.Add (pkg); - } else { - Log.DebugLine ($"Component '{pkg.Component.Name}' package exists: {pkg.LocalPackagePath}"); - LogStatus ("already downloaded", 4, Log.InfoColor); - } - } else { - Log.DebugLine ($"Component '{pkg.Component.Name}' package not downloaded yet: {pkg.LocalPackagePath}"); - LogStatus ("not downloaded yet", 4, ConsoleColor.Magenta); - toDownload.Add (pkg); - } - } - - async Task Unpack (Context context, string tempDirRoot, AndroidPackage pkg) - { - Log.StatusLine (PadStatus ($"unpacking {pkg.PackageName} to ", 4), pkg.DestinationDir, tailColor: Log.DestinationColor); - - string sevenZip = context.Tools.SevenZipPath; - Log.DebugLine ($"7z binary path: {sevenZip}"); - - string tempDir = Path.Combine (tempDirRoot, Path.GetRandomFileName ()); - - if (!await Utilities.Unpack (pkg.LocalPackagePath, tempDir)) { - Utilities.DeleteFileSilent (pkg.LocalPackagePath); - throw new InvalidOperationException ($"Failed to unpack {pkg.LocalPackagePath}"); - } - - // Clean up zip after extraction if running on a hosted azure pipelines agent. - if (context.IsRunningOnHostedAzureAgent) - Utilities.DeleteFileSilent (pkg.LocalPackagePath); - - if (pkg.Component.NoSubdirectory) { - Utilities.MoveDirectoryContentsRecursively (tempDir, pkg.DestinationDir); - return; - } - - // There should be just a single subdirectory - List subdirs = Directory.EnumerateDirectories (tempDir).ToList (); - if (subdirs.Count > 1) - throw new InvalidOperationException ($"Unexpected contents layout of Android component '{pkg.Component.Name}' - expected a single subdirectory, instead found {subdirs.Count}"); - - Utilities.MoveDirectoryContentsRecursively (subdirs [0], pkg.DestinationDir); - } - - Uri GetPackageUrl (AndroidToolchainComponent component, string packageName) - { - Uri packageUrl; - - if (component.RelativeUrl != null) - packageUrl = new Uri (AndroidToolchain.AndroidUri, component.RelativeUrl); - else - packageUrl = AndroidToolchain.AndroidUri; - - return new Uri (packageUrl, packageName); - } - - string GetDestinationDir (AndroidToolchainComponent component, string sdkRoot) - { - string path = component.DestDir; - if (!Path.IsPathRooted (path)) - return Path.Combine (sdkRoot, path); - return path; - } - - void Check (Context context, string packageCacheDir, string sdkRoot, AndroidToolchainComponent component, List toInstall, int padLeft) - { - Log.StatusLine ($" {context.Characters.Bullet} Checking ", component.Name, tailColor: ConsoleColor.White); - - if (!DependencyTypeToInstall.HasFlag (component.DependencyType)) { - LogStatus ($"skipping, did not match dependency type: {Enum.GetName(typeof(AndroidToolchainComponentType), DependencyTypeToInstall)}", padLeft, Log.InfoColor); - return; - } - - component.AddToInventory (); - - const string statusMissing = "missing"; - const string statusOutdated = "outdated"; - const string statusInstalled = "installed"; - - string path = GetDestinationDir (component, sdkRoot); - - Log.DebugLine ($"Checking if {component.Name} exists in {path}"); - bool missing; - if (IsInstalled (component, path, out missing)) { - LogStatus (statusInstalled, padLeft, Log.InfoColor); - return; - } - - if (missing) - LogStatus (statusMissing, padLeft, ConsoleColor.Magenta); - else - LogStatus (statusOutdated, padLeft, ConsoleColor.DarkYellow); - - string packageName = $"{component.Name}.zip"; - var pkg = new AndroidPackage { - Component = component, - PackageName = packageName, - Url = GetPackageUrl (component, packageName), - LocalPackagePath = Path.Combine (packageCacheDir, packageName), - DestinationDir = GetDestinationDir (component, sdkRoot), - }; - - toInstall.Add (pkg); - } - - bool IsInstalled (AndroidToolchainComponent component, string path, out bool missing) - { - missing = true; - if (!Directory.Exists (path)) { - Log.DebugLine ($"Component '{component.Name}' directory does not exist: {path}"); - return false; - } - - // This is just a cursory check, we might want to check versions - string propsFile = Path.Combine (path, "source.properties"); - if (!File.Exists (propsFile)) { - Log.DebugLine ($"Component '{component.Name}' properties file does not exist: {propsFile}"); - return false; - } - - missing = false; - if ((RefreshSdk && !IsNdk (component)) || (RefreshNdk && IsNdk (component))) { - Log.DebugLine ($"A reinstall has been requested for component '{component.Name}'"); - return false; - } - - if (String.IsNullOrEmpty (component.PkgRevision)) { - Log.DebugLine ($"Component '{component.Name}' does not specify required Pkg.Revision, assuming it's valid"); - return true; - } - - Log.DebugLine ($"Component '{component.Name}' requires Pkg.Revision to be '{component.PkgRevision}', verifying"); - var props = new JavaProperties (); - try { - using (var fs = File.OpenRead (propsFile)) { - props.Load (fs); - } - } catch (Exception ex) { - Log.DebugLine ($"Failed to read '{component.Name}' source.properties. Assuming invalid version, component will be reinstalled."); - Log.DebugLine (ex.ToString ()); - return false; - } - - string pkgRevision = props.GetProperty ("Pkg.Revision", String.Empty); - if (String.IsNullOrEmpty (pkgRevision)) { - Log.DebugLine ($"Component '{component.Name}' does not have Pkg.Revision in its source.properties file, it will be reinstalled."); - return false; - } - - if (!Utilities.ParseAndroidPkgRevision (pkgRevision, out Version? pkgVer, out string? pkgTag) || pkgVer == null) { - Log.DebugLine ($"Failed to parse a valid version from Pkg.Revision ({pkgRevision}) for component '{component.Name}'. Component will be reinstalled."); - return false; - } - - if (!Utilities.ParseAndroidPkgRevision (component.PkgRevision, out Version? expectedPkgVer, out string? expectedTag) || expectedPkgVer == null) - throw new InvalidOperationException ($"Invalid expected package version for component '{component.Name}': {component.PkgRevision}"); - - bool equal = (pkgVer == expectedPkgVer) && (pkgTag == expectedTag); - if (!equal) - Log.DebugLine ($"Installed version of '{component.Name}' ({pkgRevision}) is different than the required one ({component.PkgRevision})"); - - return equal; - } - - bool IsNdk (AndroidToolchainComponent component) - { - return component.Name.StartsWith ("android-ndk", StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets index bdd695671d1..cbb27d9959e 100644 --- a/src/androidsdk/androidsdk.targets +++ b/src/androidsdk/androidsdk.targets @@ -37,10 +37,22 @@ <_InstallAllPlatforms Condition=" '$(AndroidSdkPlatforms.ToLower())' == 'all' ">true <_InstallLatestPlatformOnly Condition=" '$(AndroidSdkPlatforms.ToLower())' == 'latest' ">true <_RequestedApiLevels>;$(AndroidSdkPlatforms.Replace(',', ';')); + + + <_NdkToolchainOSTag Condition=" '$(HostOS)' == 'Linux' ">linux-x86_64 + <_NdkToolchainOSTag Condition=" '$(HostOS)' == 'Darwin' ">darwin-x86_64 + <_NdkToolchainOSTag Condition=" '$(HostOS)' == 'Windows' ">windows-x86_64 + <_NdkHostTag Condition=" '$(HostOS)' == 'Linux' ">linux + <_NdkHostTag Condition=" '$(HostOS)' == 'Darwin' ">darwin + <_NdkHostTag Condition=" '$(HostOS)' == 'Windows' ">windows + <_NdkHostHash Condition=" '$(HostOS)' == 'Linux' ">$(XAAndroidNdkHashLinux) + <_NdkHostHash Condition=" '$(HostOS)' == 'Darwin' ">$(XAAndroidNdkHashMacOS) + <_NdkHostHash Condition=" '$(HostOS)' == 'Windows' ">$(XAAndroidNdkHashWindows) + + <_AndroidSdkPackage Include="android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip"> + $(_NdkHostHash) + $(AndroidNdkDirectory) + + + + + + + Date: Thu, 21 May 2026 14:53:15 -0500 Subject: [PATCH 06/13] [build] Use system unzip for NDK on Linux/macOS The NDK zip contains many symlinks (e.g. `clang++ -> clang`, `x86_64-linux-android24-clang -> clang`) that LibZipSharp's UnzipDirectoryChildren can't handle: on Linux it crashes with `Mono.Unix.UnixIOException: Operation not permitted [EPERM]`, on macOS it silently materializes them as zero-byte files so `clang++` later fails with `posix_spawn: Exec format error`. xaprepare worked around this by using SevenZipRunner for the NDK; the simplest equivalent here is to shell out to system `unzip` on Unix. The Windows NDK zip uses `.exe` files (no symlinks) so UnzipDirectoryChildren continues to handle it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/androidsdk/androidsdk.targets | 32 ++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets index cbb27d9959e..f5d64d3c62d 100644 --- a/src/androidsdk/androidsdk.targets +++ b/src/androidsdk/androidsdk.targets @@ -226,10 +226,18 @@ Android NDK: single per-host zip. The zip contains an `android-ndk-r/` top-level directory which `UnzipDirectoryChildren` strips by default, so the extracted contents land directly in $(AndroidNdkDirectory). + + On Unix, LibZipSharp's `SetFileProperties` chokes on the NDK's many + symlinks (e.g. `clang++ -> clang`, `x86_64-linux-android24-clang -> clang`) + and either crashes with EPERM or silently materializes them as zero-byte + files (which then fail to exec with "Exec format error"). Use the system + `unzip` binary on Unix; UnzipDirectoryChildren handles the Windows NDK + zip (no symlinks, just `.exe` files) just fine. --> <_AndroidSdkPackage Include="android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip"> $(_NdkHostHash) $(AndroidNdkDirectory) + true + + + + <_NdkZipPath>$(AndroidToolchainCacheDirectory)/android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip + <_NdkParentDir>$([System.IO.Path]::GetDirectoryName('$(AndroidNdkDirectory)')) + <_NdkExtractedDir>$(_NdkParentDir)/android-ndk-r$(_XAAndroidNdkRelease) + + + + + + + @@ -147,17 +146,17 @@ <_AndroidSdkPackage Include="cmake-$(AndroidCmakeVersion)-darwin.zip" Condition=" '$(HostOS)' == 'Darwin' "> $(XACmakeHashMacOS) $(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) - true + 0 <_AndroidSdkPackage Include="cmake-$(AndroidCmakeVersion)-linux.zip" Condition=" '$(HostOS)' == 'Linux' "> $(XACmakeHashLinux) $(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) - true + 0 <_AndroidSdkPackage Include="cmake-$(AndroidCmakeVersion)-windows.zip" Condition=" '$(HostOS)' == 'Windows' "> $(XACmakeHashWindows) $(AndroidSdkFullPath)\cmake\$(AndroidCmakeVersionPath) - true + 0 @@ -224,20 +223,12 @@ <_AndroidSdkPackage Include="android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip"> $(_NdkHostHash) $(AndroidNdkDirectory) - true - - - - - - <_NdkZipPath>$(AndroidToolchainCacheDirectory)/android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip - <_NdkParentDir>$([System.IO.Path]::GetDirectoryName('$(AndroidNdkDirectory)')) - <_NdkExtractedDir>$(_NdkParentDir)/android-ndk-r$(_XAAndroidNdkRelease) + <_StripComponents>%(_AndroidSdkPackage.StripComponents) + <_StripComponents Condition=" '$(_StripComponents)' == '' ">1 - - - - + + + + + <_NdkAbi Include="armeabi-v7a"> + arm-linux-androideabi + arm + arm + android-arm + + <_NdkAbi Include="arm64-v8a"> + aarch64-linux-android + aarch64 + aarch64 + android-arm64 + + <_NdkAbi Include="x86"> + i686-linux-android + i686 + i386 + android-x86 + + <_NdkAbi Include="x86_64"> + x86_64-linux-android + x86_64 + x86_64 + android-x64 + + <_NdkCrtFile Include="crtbegin_so.o;crtend_so.o;libc.so;libdl.so;liblog.so;libm.so;libz.so" /> + <_NdkCppAbiFile Include="libc++_static.a;libc++abi.a" /> + + - - + Outputs="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\%(_NdkAbi.Rid)\.ndk-redist-copied"> + + <_NdkToolchainRoot>$(AndroidNdkDirectory)\toolchains\llvm\prebuilt\$(_NdkToolchainOSTag) + <_NdkSysrootLib>$(_NdkToolchainRoot)\sysroot\usr\lib + + <_NdkVersionFileContent>$([System.IO.File]::ReadAllText('$(_NdkToolchainRoot)\AndroidVersion.txt')) + <_NdkLlvmMajor>$(_NdkVersionFileContent.Substring(0, $(_NdkVersionFileContent.IndexOf('.')))) + <_NdkClangLibLinux>$(_NdkToolchainRoot)\lib\clang\$(_NdkLlvmMajor)\lib\linux + + <_AbiPrefixDir>$(_NdkSysrootLib)\%(_NdkAbi.ToolchainPrefix) + <_AbiOutputDir>$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\%(_NdkAbi.Rid) + <_AbiClangArch>%(_NdkAbi.ClangArch) + <_AbiUnwindArchDir>%(_NdkAbi.UnwindArchDir) + + + <_NdkRedist Include="@(_NdkCrtFile -> '$(_AbiPrefixDir)\$(AndroidMinimumDotNetApiLevel)\%(Identity)')" /> + <_NdkRedist Include="@(_NdkCppAbiFile -> '$(_AbiPrefixDir)\%(Identity)')" /> + <_NdkRedist Include="$(_NdkClangLibLinux)\libclang_rt.builtins-$(_AbiClangArch)-android.a" /> + <_NdkRedist Include="$(_NdkClangLibLinux)\$(_AbiUnwindArchDir)\libunwind.a" /> + + + + + <_NdkRedist Remove="@(_NdkRedist)" /> + Date: Thu, 21 May 2026 15:57:52 -0500 Subject: [PATCH 09/13] [build] Replace GenerateAndroidPackageXml with template The C# task only needed Pkg.Path, Pkg.Desc, and Pkg.Revision -- all of which are already known at MSBuild evaluation time (static strings and `Configuration.props` properties). Replace the BootstrapTask with: * `package.xml.in` -- template with `@PKG_PATH@`, `@PKG_DESC@`, `@PKG_REVISION_MAJOR@`, `@PKG_REVISION_MINOR@`, `@PKG_REVISION_MICRO@` placeholders. * `_GenerateAndroidPackageXmls` -- reads the template via `File.ReadAllText(...).Replace(...)` and splits the revision via `System.Version.Parse(...).Major/.Minor/.Build`, then writes the result via ``. Deletes `GenerateAndroidPackageXml.cs` (~77 lines). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../GenerateAndroidPackageXml.cs | 77 ------------------- commit-msg.tmp | 18 +++++ src/androidsdk/androidsdk.targets | 33 ++++++-- src/androidsdk/package.xml.in | 11 +++ 4 files changed, 55 insertions(+), 84 deletions(-) delete mode 100644 build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs create mode 100644 commit-msg.tmp create mode 100644 src/androidsdk/package.xml.in diff --git a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs b/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs deleted file mode 100644 index 8acdffa65a5..00000000000 --- a/build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/GenerateAndroidPackageXml.cs +++ /dev/null @@ -1,77 +0,0 @@ -#nullable enable -using System; -using System.Collections.Generic; -using System.IO; -using System.Xml.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Xamarin.Android.Tools.BootstrapTasks -{ - // Writes a `package.xml` next to an Android SDK component's `source.properties`, - // matching the format produced by xaprepare's `Step_Android_SDK_NDK.WritePackageXmls()`. - // Used for components (e.g. the emulator) where the upstream zip omits `package.xml` but - // the Android SDK manager expects one to be present after install. - public class GenerateAndroidPackageXml : Task - { - static readonly XNamespace AndroidRepositoryCommon = "http://schemas.android.com/repository/android/common/01"; - static readonly XNamespace AndroidRepositoryGeneric = "http://schemas.android.com/repository/android/generic/01"; - - [Required] - public ITaskItem [] Directories { get; set; } = []; - - public override bool Execute () - { - foreach (var dir in Directories) { - string path = dir.ItemSpec; - var properties = ReadSourceProperties (path); - if (properties == null) { - Log.LogMessage (MessageImportance.Low, $"Skipping '{path}', no source.properties file found."); - continue; - } - string packageXml = Path.Combine (path, "package.xml"); - Log.LogMessage (MessageImportance.Low, $"Writing '{packageXml}'"); - var doc = new XDocument ( - new XElement (AndroidRepositoryCommon + "repository", - new XAttribute (XNamespace.Xmlns + "ns2", AndroidRepositoryCommon.NamespaceName), - new XAttribute (XNamespace.Xmlns + "ns3", AndroidRepositoryGeneric.NamespaceName), - new XElement ("localPackage", - new XAttribute ("path", properties ["Pkg.Path"]), - new XAttribute ("obsolete", "false"), - new XElement ("revision", GetRevision (properties ["Pkg.Revision"])), - new XElement ("display-name", properties ["Pkg.Desc"])))); - doc.Save (packageXml, SaveOptions.None); - } - - return !Log.HasLoggedErrors; - } - - static Dictionary? ReadSourceProperties (string dir) - { - var path = Path.Combine (dir, "source.properties"); - if (!File.Exists (path)) - return null; - var dict = new Dictionary (); - foreach (var line in File.ReadLines (path)) { - if (line.Length == 0) - continue; - var entry = line.Split (new [] { '=' }, 2, StringSplitOptions.None); - if (entry.Length != 2) - continue; - dict.Add (entry [0], entry [1]); - } - return dict; - } - - static IEnumerable GetRevision (string revision) - { - var parts = revision.Split ('.'); - if (parts.Length > 0) - yield return new XElement ("major", parts [0]); - if (parts.Length > 1) - yield return new XElement ("minor", parts [1]); - if (parts.Length > 2) - yield return new XElement ("micro", parts [2]); - } - } -} diff --git a/commit-msg.tmp b/commit-msg.tmp new file mode 100644 index 00000000000..fb10d1a0d8a --- /dev/null +++ b/commit-msg.tmp @@ -0,0 +1,18 @@ +[build] Replace GenerateAndroidPackageXml with template + +The C# task only needed Pkg.Path, Pkg.Desc, and Pkg.Revision -- all of +which are already known at MSBuild evaluation time (static strings and +`Configuration.props` properties). Replace the BootstrapTask with: + +* `package.xml.in` -- template with `@PKG_PATH@`, `@PKG_DESC@`, + `@PKG_REVISION_MAJOR@`, `@PKG_REVISION_MINOR@`, `@PKG_REVISION_MICRO@` + placeholders. + +* `_GenerateAndroidPackageXmls` -- reads the template via + `File.ReadAllText(...).Replace(...)` and splits the revision via + `System.Version.Parse(...).Major/.Minor/.Build`, then writes the result + via ``. + +Deletes `GenerateAndroidPackageXml.cs` (~77 lines). + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> \ No newline at end of file diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets index fae5b44a4ab..1f0c9cba743 100644 --- a/src/androidsdk/androidsdk.targets +++ b/src/androidsdk/androidsdk.targets @@ -50,8 +50,6 @@ <_NdkHostHash Condition=" '$(HostOS)' == 'Windows' ">$(XAAndroidNdkHashWindows) - - + <_AndroidSdkPackage Include="emulator-darwin_x64-$(EmulatorVersion).zip" Condition=" '$(HostOS)' == 'Darwin' and '$(_IsArm64Apple)' != 'true' "> $(XAEmulatorHashMacOSx64) $(AndroidSdkFullPath)\emulator true + emulator + Android Emulator + $(EmulatorPkgRevision) <_AndroidSdkPackage Include="emulator-darwin_aarch64-$(EmulatorVersion).zip" Condition=" '$(_IsArm64Apple)' == 'true' "> $(XAEmulatorHashMacOSArm64) $(AndroidSdkFullPath)\emulator true + emulator + Android Emulator + $(EmulatorPkgRevision) <_AndroidSdkPackage Include="emulator-linux_x64-$(EmulatorVersion).zip" Condition=" '$(HostOS)' == 'Linux' "> $(XAEmulatorHashLinux) $(AndroidSdkFullPath)\emulator true + emulator + Android Emulator + $(EmulatorPkgRevision) <_AndroidSdkPackage Include="emulator-windows_x64-$(EmulatorVersion).zip" Condition=" '$(HostOS)' == 'Windows' "> $(XAEmulatorHashWindows) $(AndroidSdkFullPath)\emulator true + emulator + Android Emulator + $(EmulatorPkgRevision) - <_PackageXmlDirectory Include="@(_AndroidSdkPackage->'%(Destination)')" + <_PkgXmlItem Include="@(_AndroidSdkPackage)" Condition=" '%(_AndroidSdkPackage.GeneratePackageXml)' == 'true' " /> - + + + + + @PKG_REVISION_MAJOR@ + @PKG_REVISION_MINOR@ + @PKG_REVISION_MICRO@ + + @PKG_DESC@ + + From 5a4849d2cbd9262058d8128863886dd3d1a84267 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 15:58:03 -0500 Subject: [PATCH 10/13] Remove stray commit-msg.tmp file Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- commit-msg.tmp | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 commit-msg.tmp diff --git a/commit-msg.tmp b/commit-msg.tmp deleted file mode 100644 index fb10d1a0d8a..00000000000 --- a/commit-msg.tmp +++ /dev/null @@ -1,18 +0,0 @@ -[build] Replace GenerateAndroidPackageXml with template - -The C# task only needed Pkg.Path, Pkg.Desc, and Pkg.Revision -- all of -which are already known at MSBuild evaluation time (static strings and -`Configuration.props` properties). Replace the BootstrapTask with: - -* `package.xml.in` -- template with `@PKG_PATH@`, `@PKG_DESC@`, - `@PKG_REVISION_MAJOR@`, `@PKG_REVISION_MINOR@`, `@PKG_REVISION_MICRO@` - placeholders. - -* `_GenerateAndroidPackageXmls` -- reads the template via - `File.ReadAllText(...).Replace(...)` and splits the revision via - `System.Version.Parse(...).Major/.Minor/.Build`, then writes the result - via ``. - -Deletes `GenerateAndroidPackageXml.cs` (~77 lines). - -Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> \ No newline at end of file From c77523c1ddf99cc8efe94fa131ee4278f926477a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 21 May 2026 16:26:28 -0500 Subject: [PATCH 11/13] [build] Use Path.Combine for tar source path on Unix The Exec command passes the path directly to the shell, so '\' on macOS/Linux is not a separator. Use System.IO.Path.Combine so MSBuild produces the right separator for the host. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/androidsdk/androidsdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/androidsdk/androidsdk.targets b/src/androidsdk/androidsdk.targets index 1f0c9cba743..b0c09f3db5d 100644 --- a/src/androidsdk/androidsdk.targets +++ b/src/androidsdk/androidsdk.targets @@ -319,7 +319,7 @@ - + <_AndroidSdkPackage Include="android-ndk-r$(_XAAndroidNdkRelease)-$(_NdkHostTag).zip"> $(_NdkHostHash) $(AndroidNdkDirectory) + true + android-ndk-r$(_XAAndroidNdkRelease) <_StripComponents>%(_AndroidSdkPackage.StripComponents) <_StripComponents Condition=" '$(_StripComponents)' == '' ">1 + <_UseUnzip Condition=" '$(HostOS)' != 'Windows' and '%(_AndroidSdkPackage.UseUnzipOnUnix)' == 'true' ">true + <_ZipPath>$([System.IO.Path]::Combine('$(AndroidToolchainCacheDirectory)', '%(_AndroidSdkPackage.Identity)')) + <_DestParentDir Condition=" '$(_UseUnzip)' == 'true' ">$([System.IO.Path]::GetDirectoryName('%(_AndroidSdkPackage.Destination)')) - + + + +