diff --git a/build-tools/create-packs/Microsoft.Android.Runtime.proj b/build-tools/create-packs/Microsoft.Android.Runtime.proj index 93452ea06b6..7722a8ba636 100644 --- a/build-tools/create-packs/Microsoft.Android.Runtime.proj +++ b/build-tools/create-packs/Microsoft.Android.Runtime.proj @@ -108,6 +108,22 @@ 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 ); diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools index 981df21b947..7d0e083ed8e 160000 --- a/external/xamarin-android-tools +++ b/external/xamarin-android-tools @@ -1 +1 @@ -Subproject commit 981df21b947eb16fd0d1910c2cf888f2030323f6 +Subproject commit 7d0e083ed8e4dad3686973cc359e9c4aad57e3aa 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 6138e7e2e8e..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 @@ -307,49 +307,79 @@ 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) - - - <_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)" /> $(NativeBinaryPrefix)$(TargetName).so PreserveNewest + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs index 491ece9dd1b..b1ade49afd5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs @@ -131,7 +131,6 @@ bool LinkForAbi (string abi) HashStyleBoth = true, LittleEndian = true, EntryPoint = "0x0", - CompressDebugSections = "zlib", }; if (!ExportsFile.IsNullOrEmpty ()) { 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/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..d830c8cb77a --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeAotBuildTests.cs @@ -0,0 +1,54 @@ +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; + // 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=" }), + "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." + ); + } + } +} 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..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 @@ -104,6 +104,11 @@ public IEnumerable LastBuildOutput { /// public bool AutomaticNuGetRestore { get; set; } = true; + /// + /// When true, skip emitting AndroidNdkDirectory into the test response file. + /// + public bool SkipNdkDirectory { get; set; } = false; + /// /// Checks whether cross-compilers are available for the specified Android ABIs. /// @@ -276,7 +281,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 (); 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);