From 97c141418f13cec9758ed5847898b98ca70384be Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 17 Apr 2026 10:27:19 -0700 Subject: [PATCH 1/4] [NativeAOT] Take over native linker invocation from ILC targets Set NativeLib=static so ILC produces a .a archive via ar instead of invoking the linker directly. Add _AndroidLinkNativeAotSharedLibrary target that runs after LinkNative and links the ILC .o output into a .so using the NDK clang wrapper. This gives Android full control over the native linker invocation, following the same approach used by macios. Reproduce the flags that LinkNative and SetupOSSpecificProps would have provided for NativeLib=Shared: - -shared, -Wl,-e,0x0, -Wl,-z,max-page-size=16384 (from LinkerArg) - --version-script, --export-dynamic, --discard-all, --gc-sections (from CustomLinkerArg inside LinkNative) - -fuse-ld=lld (from LinkerArg via LinkerFlavor) - sections.ld linker script to retain the __modules section Set IlcExportUnmanagedEntrypoints=true so ILC exports [UnmanagedCallersOnly] methods as native symbols, required for JNI entry points. Clear LinkerFlavor inside _AndroidBeforeIlcCompile to work around an ILC targets bug where _LinkerVersion detection is skipped for NativeLib=Static but the numeric comparison in LinkNative still evaluates. Context: https://github.com/dotnet/runtime/issues/126978 The resulting linker command line is identical to the original. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.Android.Sdk.NativeAOT.targets | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 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 ddf8348d9e9..72c62edf700 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 @@ -25,6 +25,12 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. <_GenerateProguardAfterTargets>_AndroidComputeIlcCompileInputs + + static + + true @@ -111,6 +117,12 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. false + + + true - + @@ -258,6 +270,49 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. DependsOnTargets="_GenerateNativeAotAndroidAppAssemblerSources"> + + + + <_AndroidNativeAotSharedLibrary>$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so + + + <_AndroidNativeAotLinkerArgs Include=""$(NativeObject)"" /> + <_AndroidNativeAotLinkerArgs Include="-shared" /> + <_AndroidNativeAotLinkerArgs Include="-fuse-ld=lld" /> + <_AndroidNativeAotLinkerArgs Include="-o "$(_AndroidNativeAotSharedLibrary)"" /> + + <_AndroidNativeAotLinkerArgs Include="-Wl,-e,0x0" /> + <_AndroidNativeAotLinkerArgs Include="-Wl,-z,max-page-size=16384" /> + <_AndroidNativeAotLinkerArgs Include="-Wl,--version-script="$(ExportsFile)"" Condition="'$(ExportsFile)' != ''" /> + <_AndroidNativeAotLinkerArgs Include="-Wl,--export-dynamic" Condition="'$(ExportsFile)' != ''" /> + <_AndroidNativeAotLinkerArgs Include="-Wl,--discard-all" /> + <_AndroidNativeAotLinkerArgs Include="-Wl,--gc-sections" /> + <_AndroidNativeAotLinkerArgs Include="-Wl,-T,"$(NativeIntermediateOutputPath)sections.ld"" /> + <_AndroidNativeAotLinkerArgs Include="@(LinkerArg)" /> + + + + + + + + + + + + + $(NativeBinaryPrefix)$(TargetName).so + PreserveNewest + + + + - + From ec129aa6b5a20e67b3e0b1be39aa047054c57389 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 17 Apr 2026 11:15:44 -0700 Subject: [PATCH 2/4] Add Inputs/Outputs and FileWrites for incremental build support Add Inputs/Outputs to _AndroidLinkNativeAotSharedLibrary so incremental builds can skip relinking when inputs haven't changed. Add FileWrites for the .so and sections.ld so Clean can account for generated files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../targets/Microsoft.Android.Sdk.NativeAOT.targets | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 72c62edf700..b5baf486522 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 @@ -276,7 +276,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. This target runs after LinkNative and produces the shared library that Android needs. --> + AfterTargets="LinkNative" + Inputs="$(NativeObject);@(NativeLibrary)" + Outputs="$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so"> <_AndroidNativeAotSharedLibrary>$(NativeOutputPath)$(NativeBinaryPrefix)$(TargetName).so @@ -310,6 +312,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. $(NativeBinaryPrefix)$(TargetName).so PreserveNewest + + From 2ff10fb2a7ce47bf163db58187d0fd4454790994 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 22 Apr 2026 14:58:17 -0700 Subject: [PATCH 3/4] Use response file for linker args on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows, ILC's LinkNative writes linker args to a response file instead of passing them inline — cmd.exe has quoting issues with spaces in paths. Match that behavior in _AndroidLinkNativeAotSharedLibrary. Fixes NativeAOT build failures for projects with spaces or special characters in their names (e.g. CheckProjectWithSpaceInNameWorks tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../targets/Microsoft.Android.Sdk.NativeAOT.targets | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 591704295f9..bd60e38889a 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 @@ -303,7 +303,14 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. - + + + + @@ -314,6 +321,7 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. + From fd6b78ecb2e232f68ce1b8cf7e6f3433d41cdbf9 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 24 Apr 2026 06:08:49 +0800 Subject: [PATCH 4/4] Set CppLibCreator to llvm-ar for NativeAOT static library builds ILC's LinkNative target defaults CppLibCreator to the host 'ar', which does not understand ELF objects when cross-compiling for Android. On macOS this caused Xcode's ranlib to emit spurious 'empty table of contents' warnings for every ABI. Set CppLibCreator to llvm-ar (from the NDK toolchain, already on PATH) so the archiver can correctly process ELF .o files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../targets/Microsoft.Android.Sdk.NativeAOT.targets | 3 +++ 1 file changed, 3 insertions(+) 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 bd60e38889a..8de77b2c654 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 @@ -113,6 +113,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android. $(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) $(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt) llvm-objcopy + + llvm-ar false