From 47e7301858043b3c5a12825afea3184ee743b6fd Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 4 May 2026 13:11:09 -0700 Subject: [PATCH 01/10] Add NativeAOT no-NDK smoke test Add NativeAotBuildTests as the dedicated test class for NativeAOT-specific build tests. First test verifies that NativeAOT builds fail when the NDK is not available, by clearing AndroidNdkDirectory via build parameters. This test will be flipped to assert success when the workload pack ships its own linker and sysroot files, removing the NDK dependency. Context: https://github.com/dotnet/android/issues/10697 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../NativeAotBuildTests.cs | 35 +++++++++++++++++++ .../Xamarin.ProjectTools/Common/Builder.cs | 3 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs new file mode 100644 index 00000000000..15e15d028dc --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using Xamarin.Android.Tasks; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests +{ + /// + /// Build tests specific to the NativeAOT runtime. + /// + [TestFixture] + [Category ("Node-2")] + public class NativeAotBuildTests : BaseTest + { + [Test] + public void BuildNativeAot_WithoutNdk_Fails () + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + + using var builder = CreateApkBuilder (); + builder.ThrowOnBuildFailure = false; + // Clear AndroidNdkDirectory to simulate a machine without NDK installed. + // This overrides the rsp-injected value (MSBuild last-value-wins). + // + // Currently NativeAOT linking requires NDK tools, so the build should fail. + // When the workload pack ships its own linker, flip this to Assert.IsTrue. + Assert.IsFalse ( + builder.Build (proj, parameters: new [] { "/p:AndroidNdkDirectory=\"\"" }), + "Build should have failed without NDK." + ); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs index 753f6d7a9fa..01ccb4cf97a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs @@ -103,6 +103,7 @@ public IEnumerable LastBuildOutput { /// True if NuGet restore occurs automatically (default) /// public bool AutomaticNuGetRestore { get; set; } = true; + public bool SkipNdkDirectory { get; set; } = false; /// /// Checks whether cross-compilers are available for the specified Android ABIs. @@ -276,7 +277,7 @@ protected bool BuildInternal (string projectOrSolution, string target, string [] sw.WriteLine ("/p:AndroidSdkDirectory=\"{0}\"", sdkPath); } string ndkPath = AndroidSdkResolver.GetAndroidNdkPath (); - if (Directory.Exists (ndkPath)) { + if (!SkipNdkDirectory && Directory.Exists (ndkPath)) { sw.WriteLine ("/p:AndroidNdkDirectory=\"{0}\"", ndkPath); } string jdkPath = AndroidSdkResolver.GetJavaSdkPath (); From c9890dcbbfd1ecdf46cb6f9e83f2b3d697bda902 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 4 May 2026 13:50:11 -0700 Subject: [PATCH 02/10] Include CRT objects in NativeAOT runtime pack Add crtbegin_so.o and crtend_so.o to the NativeAOT runtime pack. These were only included for CoreCLR. They are needed for NDK-free linking where the workload pack provides all linker dependencies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/create-packs/Microsoft.Android.Runtime.proj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 93452ea06b6..3ef10f9edff 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -108,6 +108,10 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll" /> + + + + From 93eee9d1c4d3a140ad8578e77c2d31608ea2c81e Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 7 May 2026 10:04:49 -0700 Subject: [PATCH 03/10] WIP: system lib stubs fix and workload linker test --- .../Microsoft.Android.Runtime.proj | 2 +- external/Java.Interop | 2 +- external/xamarin-android-tools | 2 +- .../Microsoft.Android.Sdk.NativeAOT.targets | 68 ++++++++++++++----- .../Tasks/LinkNativeAotSharedLibrary.cs | 6 +- .../NativeAotBuildTests.cs | 21 +++++- 6 files changed, 78 insertions(+), 23 deletions(-) diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 3ef10f9edff..95cdce404fd 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -95,7 +95,7 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. - + diff --git a/external/Java.Interop b/external/Java.Interop index 05e9e8f024b..dedb8f86d5e 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 05e9e8f024b509df785a52fc2562833e173857d6 +Subproject commit dedb8f86d5e7f449936fe3fc260320fc24cf0033 diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools index 981df21b947..2fd1240b2bf 160000 --- a/external/xamarin-android-tools +++ b/external/xamarin-android-tools @@ -1 +1 @@ -Subproject commit 981df21b947eb16fd0d1910c2cf888f2030323f6 +Subproject commit 2fd1240b2bfa3bea40d7efc16ef4935734cc7f50 diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 6138e7e2e8e..6593bc7d053 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -307,49 +307,80 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. Outputs="$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so"> <_AndroidNativeAotSharedLibrary>$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so - + <_AndroidUseWorkloadNativeLinker Condition=" '$(_AndroidUseWorkloadNativeLinker)' == '' ">false + + + + + <_NativeAotRuntimePackAsset Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" /> + + + <_NativeAotRuntimePackNativeDir>@(_NativeAotRuntimePackAsset->'%(RootDir)%(Directory)') + <_NativeAotLinkerBinDir>$(AndroidBinUtilsDirectory) + + + + <_NdkApiSysrootDir>$(_NdkSysrootDir)$(_NDKApiLevel)/ <_NdkClangResourceDir>$(_AndroidNdkDirectory)toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/lib/clang + <_NativeAotLinkerBinDir>$(_NdkBinDir) + <_NativeAotCompressDebugSections>zlib - - - <_NativeAotCrtStartFiles Include="$(_NdkApiSysrootDir)crtbegin_so.o"> + + + <_NativeAotCrtStartFiles Include="$(_NativeAotRuntimePackNativeDir)/crtbegin_so.o"> @(_PrivateBuildTargetAbi) + <_NativeAotCrtEndFiles Include="$(_NativeAotRuntimePackNativeDir)/crtend_so.o"> + @(_PrivateBuildTargetAbi) + + <_NativeAotLibSearchPaths Include="$(_NativeAotRuntimePackNativeDir)" /> + <_NativeAotCompilerRtLibs Include="$(_NativeAotRuntimePackNativeDir)/libclang_rt.builtins-$(_NdkAbi)-android.a" /> + <_NativeAotCompilerRtLibs Include="$(_NativeAotRuntimePackNativeDir)/libunwind.a" /> + - + + + <_NativeAotCrtStartFiles Include="$(_NdkApiSysrootDir)crtbegin_so.o"> + @(_PrivateBuildTargetAbi) + <_NativeAotCrtEndFiles Include="$(_NdkApiSysrootDir)crtend_so.o"> @(_PrivateBuildTargetAbi) - - <_NativeAotLibSearchPaths Include="$(_NdkApiSysrootDir)" /> <_NativeAotLibSearchPaths Include="$(_NdkSysrootDir)" /> + <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/libclang_rt.builtins-$(_NdkAbi)-android.a" /> + <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/$(_NdkAbi)/libunwind.a" /> + - - + + <_NativeAotLinkLibraries Include="@(NativeLibrary)" /> - <_NativeAotLinkLibraries Include="@(_NdkLibs)" /> - - <_NativeAotAdditionalObjects Include="@(_PrivateJniInitFuncsNativeObjectFile)" /> <_NativeAotAdditionalObjects Include="@(_PrivateEnvironmentNativeObjectFile)" /> - - <_NativeAotSystemLibraries Include="dl" /> <_NativeAotSystemLibraries Include="z" /> <_NativeAotSystemLibraries Include="log" /> <_NativeAotSystemLibraries Include="m" /> <_NativeAotSystemLibraries Include="c" /> + - - <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/libclang_rt.builtins-$(_NdkAbi)-android.a" /> - <_NativeAotCompilerRtLibs Include="$(_NdkClangResourceDir)/**/$(_NdkAbi)/libunwind.a" /> + + + <_NativeAotLinkLibraries Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" /> + <_NativeAotLinkLibraries Include="$(_NativeAotRuntimePackNativeDir)libc++_static.a" /> + <_NativeAotLinkLibraries Include="$(_NativeAotRuntimePackNativeDir)libc++abi.a" /> + + + <_NativeAotLinkLibraries Include="@(_NdkLibs)" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs index 491ece9dd1b..3b06367c358 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs @@ -94,6 +94,10 @@ public class LinkNativeAotSharedLibrary : AndroidTask /// public string? ExtraLinkerArgs { get; set; } + /// + /// Compression format for debug sections (e.g., "zlib"). Leave empty to skip. + /// + public string? CompressDebugSections { get; set; } [Required] public string SupportedAbis { get; set; } = ""; @@ -131,7 +135,7 @@ bool LinkForAbi (string abi) HashStyleBoth = true, LittleEndian = true, EntryPoint = "0x0", - CompressDebugSections = "zlib", + CompressDebugSections = CompressDebugSections, }; if (!ExportsFile.IsNullOrEmpty ()) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs index 15e15d028dc..2be8b91c24a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs @@ -27,9 +27,28 @@ public void BuildNativeAot_WithoutNdk_Fails () // Currently NativeAOT linking requires NDK tools, so the build should fail. // When the workload pack ships its own linker, flip this to Assert.IsTrue. Assert.IsFalse ( - builder.Build (proj, parameters: new [] { "/p:AndroidNdkDirectory=\"\"" }), + builder.Build (proj, parameters: new [] { "AndroidNdkDirectory=\"\"" }), "Build should have failed without NDK." ); } + + [Test] + public void BuildNativeAot_WithWorkloadLinker_WithoutNdk () + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + + using var builder = CreateApkBuilder (); + // Use workload-provided linker and sysroot instead of NDK + Assert.IsTrue ( + builder.Build (proj, parameters: new [] { + "AndroidNdkDirectory=\"\"", + "_AndroidUseWorkloadNativeLinker=true", + }), + "Build should succeed with workload linker and no NDK." + ); + } } } From 66e66010b98498c9635d7cf68fa7279abc9ba716 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 5 May 2026 10:59:11 -0700 Subject: [PATCH 04/10] Add NativeAOT-specific redist directory with API 24 stubs NativeAOT links dotnet/runtime's libSystem.Native.a which was compiled against API 24 and references symbols like __gnu_strerror_r (API 23+) and __write_chk (API 24+). The existing redist/ directory has API 21 stubs which lack these symbols. Add a separate redist-nativeaot/ directory with CRT objects and system lib stubs from the API 24 sysroot. The NativeAOT runtime pack sources from this directory instead of the shared redist/ (which stays at API 21 for Mono/CoreCLR compatibility). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../create-packs/Microsoft.Android.Runtime.proj | 14 ++++++++++---- .../xaprepare/Steps/Step_Android_SDK_NDK.cs | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 95cdce404fd..9a0f90e4631 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -95,7 +95,7 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. - + @@ -109,9 +109,15 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. <_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll" /> - - - + + + + + + + + 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 d987b23c878..0743a1e0dec 100644 --- a/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs +++ b/build-tools/xaprepare/xaprepare/Steps/Step_Android_SDK_NDK.cs @@ -229,17 +229,29 @@ bool CopyRedistributableFiles (Context context) foreach (string file in ClangArchFiles) { CopyFile (abi, clangArchLibPath, file); } + + // NativeAOT only supports 64-bit ABIs and links against dotnet/runtime's + // libSystem.Native.a which was compiled targeting a higher API level. Copy a + // second set of CRT/system lib stubs from the higher API level sysroot for the + // NativeAOT runtime pack. + bool is64BitAbi = abi == "arm64-v8a" || abi == "x86_64"; + if (is64BitAbi) { + string nativeAotCrtFilesPath = Path.Combine (abiDir, BuildAndroidPlatforms.NdkMinimumNonMonoAPI); + foreach (string file in CRTFiles) { + CopyFile (abi, nativeAotCrtFilesPath, file, redistDirName: "redist-nativeaot"); + } + } } return true; - void CopyFile (string abi, string sourceDir, string fileName) + void CopyFile (string abi, string sourceDir, string fileName, string? redistDirName = null) { 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), + redistDirName ?? context.Properties.GetRequiredValue (KnownProperties.RuntimeRedistDirName), rid ); From b75bc5ee50c5be2bc668fbcde22e3578f5653194 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 7 May 2026 11:16:53 -0700 Subject: [PATCH 05/10] Update submodules to match main Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Java.Interop | 2 +- external/xamarin-android-tools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/Java.Interop b/external/Java.Interop index dedb8f86d5e..05e9e8f024b 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit dedb8f86d5e7f449936fe3fc260320fc24cf0033 +Subproject commit 05e9e8f024b509df785a52fc2562833e173857d6 diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools index 2fd1240b2bf..7d0e083ed8e 160000 --- a/external/xamarin-android-tools +++ b/external/xamarin-android-tools @@ -1 +1 @@ -Subproject commit 2fd1240b2bfa3bea40d7efc16ef4935734cc7f50 +Subproject commit 7d0e083ed8e4dad3686973cc359e9c4aad57e3aa From 099b90a10b91d074f636f6b4a509d928966b5837 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 7 May 2026 11:33:58 -0700 Subject: [PATCH 06/10] Include toolchain libraries in NativeAOT runtime pack Add libc++_static.a, libc++abi.a, libclang_rt.builtins, and libunwind.a to the NativeAOT pack. These were only in the CoreCLR pack but NativeAOT needs them for the workload linker path (no NDK). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/create-packs/Microsoft.Android.Runtime.proj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 9a0f90e4631..7722a8ba636 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -118,6 +118,12 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+. + + + + + + From 891a72d6f32d0b3eb254b2ef69c90407c79934dd Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 7 May 2026 11:52:19 -0700 Subject: [PATCH 07/10] Remove CompressDebugSections from NativeAOT link step Neither ILC nor the CoreCLR/Mono link paths pass --compress-debug-sections. This was added without justification and created unnecessary complexity for the workload linker path (whose LLD lacks zlib support). Tracked in #11304 for future enablement across all runtimes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../targets/Microsoft.Android.Sdk.NativeAOT.targets | 2 -- .../Tasks/LinkNativeAotSharedLibrary.cs | 5 ----- 2 files changed, 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index 6593bc7d053..d3ff25ba90e 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -326,7 +326,6 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_NdkApiSysrootDir>$(_NdkSysrootDir)$(_NDKApiLevel)/ <_NdkClangResourceDir>$(_AndroidNdkDirectory)toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/lib/clang <_NativeAotLinkerBinDir>$(_NdkBinDir) - <_NativeAotCompressDebugSections>zlib @@ -396,7 +395,6 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. LinkerScript="$(NativeIntermediateOutputPath)sections.ld" LinkerScriptContent="OVERWRITE_SECTIONS { __modules : { KEEP(*(__modules)) } }" ExtraLinkerArgs="$(_AndroidNativeAotExtraLinkerArgs)" - CompressDebugSections="$(_NativeAotCompressDebugSections)" SupportedAbis="@(_PrivateBuildTargetAbi)" DebugBuild="$(AndroidIncludeDebugSymbols)" /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs index 3b06367c358..b1ade49afd5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs @@ -94,10 +94,6 @@ public class LinkNativeAotSharedLibrary : AndroidTask /// public string? ExtraLinkerArgs { get; set; } - /// - /// Compression format for debug sections (e.g., "zlib"). Leave empty to skip. - /// - public string? CompressDebugSections { get; set; } [Required] public string SupportedAbis { get; set; } = ""; @@ -135,7 +131,6 @@ bool LinkForAbi (string abi) HashStyleBoth = true, LittleEndian = true, EntryPoint = "0x0", - CompressDebugSections = CompressDebugSections, }; if (!ExportsFile.IsNullOrEmpty ()) { From 06aac9a5942e7ae38270c259d849de9853cd1758 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 7 May 2026 11:54:44 -0700 Subject: [PATCH 08/10] Address PR review feedback - Add XML doc comment for SkipNdkDirectory property - Clarify test comment: default path still requires NDK Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs | 5 ++--- .../Tests/Xamarin.ProjectTools/Common/Builder.cs | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs index 2be8b91c24a..53935e8ebb4 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs @@ -23,9 +23,8 @@ public void BuildNativeAot_WithoutNdk_Fails () builder.ThrowOnBuildFailure = false; // Clear AndroidNdkDirectory to simulate a machine without NDK installed. // This overrides the rsp-injected value (MSBuild last-value-wins). - // - // Currently NativeAOT linking requires NDK tools, so the build should fail. - // When the workload pack ships its own linker, flip this to Assert.IsTrue. + // The default path (without _AndroidUseWorkloadNativeLinker) still requires + // the NDK, so the build should fail. Assert.IsFalse ( builder.Build (proj, parameters: new [] { "AndroidNdkDirectory=\"\"" }), "Build should have failed without NDK." diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs index 01ccb4cf97a..24151e8e3da 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/Builder.cs @@ -103,6 +103,10 @@ public IEnumerable LastBuildOutput { /// True if NuGet restore occurs automatically (default) /// public bool AutomaticNuGetRestore { get; set; } = true; + + /// + /// When true, skip emitting AndroidNdkDirectory into the test response file. + /// public bool SkipNdkDirectory { get; set; } = false; /// From bcccc62fb08ba0bfb4a38978d10045c1354217c2 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 8 May 2026 09:50:46 -0700 Subject: [PATCH 09/10] Remove sysroot stub .so files from NativeAOT publish output Enable ProcessRuntimePackLibraryDirectories for NativeAOT by removing the != NativeAOT condition and adding the NativeAOT package prefix to IsInSupportedRuntimePack. This reuses the existing CoreCLR/Mono mechanism to remove sysroot stub .so files (libc.so, libdl.so, etc.) from ResolvedFileToPublish so they don't end up in the APK. Also make DSOWrapperGenerator.GetConfig skip gracefully when libarchive-dso-stub.so is not found in the runtime pack directory, since the NativeAOT pack doesn't include this CoreCLR/Mono-specific file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../targets/Microsoft.Android.Sdk.Aot.targets | 1 - .../targets/Microsoft.Android.Sdk.AssemblyResolution.targets | 1 - .../targets/Microsoft.Android.Sdk.NativeAOT.targets | 1 + .../Tasks/ProcessRuntimePackLibraryDirectories.cs | 3 ++- .../Utilities/DSOWrapperGenerator.cs | 3 ++- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets index 3537c75fb4b..2a214899948 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Aot.targets @@ -83,7 +83,6 @@ They run in a context of an inner build with a single $(RuntimeIdentifier). runs in an inner build and `_RuntimePackLibraryDirectory` items aren't carried over to it. --> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 60133cb930a..efb8c209f7b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -144,7 +144,6 @@ _ResolveAssemblies MSBuild target. are never taken into consideration in any context. --> diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets index d3ff25ba90e..092f9a601f0 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets @@ -406,6 +406,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. $(NativeBinaryPrefix)$(TargetName).so PreserveNewest + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs index 142a0dccde6..c4d10e2f3f9 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessRuntimePackLibraryDirectories.cs @@ -95,6 +95,7 @@ bool IsInSupportedRuntimePack (ITaskItem item) } return NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.CoreCLR.", StringComparison.OrdinalIgnoreCase) || - NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.Mono.", StringComparison.OrdinalIgnoreCase); + NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.Mono.", StringComparison.OrdinalIgnoreCase) || + NuGetPackageId.StartsWith ("Microsoft.Android.Runtime.NativeAOT.", StringComparison.OrdinalIgnoreCase); } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs index ff584246f61..b0d450822fa 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/DSOWrapperGenerator.cs @@ -68,7 +68,8 @@ public static Config GetConfig (TaskLoggingHelper log, string androidBinUtilsDir string stubPath = Path.Combine (packLibDir.ItemSpec, StubFileName); if (!File.Exists (stubPath)) { - throw new InvalidOperationException ($"Internal error: archive DSO stub file '{stubPath}' does not exist in runtime pack at {packLibDir}"); + log.LogDebugMessage ($"Archive DSO stub file '{stubPath}' not found in runtime pack directory, skipping"); + continue; } AndroidTargetArch arch = MonoAndroidHelper.RidToArch (packRID); From d4f2671f1fef162dda11d35707bfd2c3452946d2 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 8 May 2026 13:51:07 -0700 Subject: [PATCH 10/10] Fix NativeAOT test: override _AndroidNdkDirectory to prevent NDK discovery Setting AndroidNdkDirectory to empty is not sufficient because ResolveSdks has a fallback chain (SDK ndk-bundle dir, environment variables, PATH) that discovers the NDK regardless of the input hint. Override _AndroidNdkDirectory (the internal resolved path) via /p: which has highest MSBuild precedence and cannot be overridden by task outputs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../NativeAotBuildTests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs index 53935e8ebb4..d830c8cb77a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs @@ -21,12 +21,13 @@ public void BuildNativeAot_WithoutNdk_Fails () using var builder = CreateApkBuilder (); builder.ThrowOnBuildFailure = false; - // Clear AndroidNdkDirectory to simulate a machine without NDK installed. - // This overrides the rsp-injected value (MSBuild last-value-wins). - // The default path (without _AndroidUseWorkloadNativeLinker) still requires - // the NDK, so the build should fail. + // Override _AndroidNdkDirectory (the internal resolved path) to simulate no NDK. + // Setting AndroidNdkDirectory alone is not sufficient because ResolveSdks has a + // fallback chain that discovers NDK from the SDK directory, environment variables, + // and other standard locations. /p: has highest MSBuild precedence and cannot be + // overridden by task outputs. Assert.IsFalse ( - builder.Build (proj, parameters: new [] { "AndroidNdkDirectory=\"\"" }), + builder.Build (proj, parameters: new [] { "_AndroidNdkDirectory=" }), "Build should have failed without NDK." ); } @@ -43,7 +44,7 @@ public void BuildNativeAot_WithWorkloadLinker_WithoutNdk () // Use workload-provided linker and sysroot instead of NDK Assert.IsTrue ( builder.Build (proj, parameters: new [] { - "AndroidNdkDirectory=\"\"", + "_AndroidNdkDirectory=", "_AndroidUseWorkloadNativeLinker=true", }), "Build should succeed with workload linker and no NDK."