Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions build-tools/create-packs/Microsoft.Android.Runtime.proj
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+.
</ItemGroup>

<ItemGroup Condition=" '$(AndroidRuntime)' == 'NativeAOT' ">
<!-- Include sysroot libraries for NativeAOT linking -->
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libc.so" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libdl.so" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\liblog.so" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libm.so" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libz.so" />
<!-- C++ runtime and compiler builtins for NativeAOT -->
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\crtbegin_so.o" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\crtend_so.o" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libc++_static.a" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libc++abi.a" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libclang_rt.builtins-$(_ClangArch)-android.a" />
<NativeRuntimeAsset Include="$(NativeRuntimeOutputRootDir)$(_RuntimeRedistDirName)\$(AndroidRID)\libunwind.a" />
<!-- NativeAOT runtime libraries -->
<NativeRuntimeAsset Condition=" Exists('$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.debug-static-debug.a') " Include="$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.debug-static-debug.a" />
<NativeRuntimeAsset Condition=" Exists('$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.release-static-release.a') " Include="$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.release-static-release.a" />
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ _ResolveAssemblies MSBuild target.
</PropertyGroup>

<PropertyGroup>
<!-- When marshal methods are enabled, AOT needs to run after the GenerateJavaStubs task -->
<_RunAotMaybe Condition=" '$(_AndroidUseMarshalMethods)' != 'True' ">_AndroidAot</_RunAotMaybe>
<!--
When marshal methods are enabled, AOT needs to run after the GenerateJavaStubs task.
For NativeAOT, _RunAotMaybe is set in Microsoft.Android.Sdk.NativeAOT.targets.
-->
<_RunAotMaybe Condition=" '$(_AndroidUseMarshalMethods)' != 'True' and '$(_AndroidRuntime)' != 'NativeAOT' ">_AndroidAot</_RunAotMaybe>
</PropertyGroup>

<Target Name="_ComputeFilesToPublishForRuntimeIdentifiers"
Expand Down Expand Up @@ -113,7 +116,6 @@ _ResolveAssemblies MSBuild target.
are never taken into consideration in any context.
-->
<ProcessRuntimePackLibraryDirectories
Condition=" '$(_AndroidRuntime)' != 'NativeAOT' "
ResolvedFilesToPublish="@(ResolvedFileToPublish)">
<Output TaskParameter="RuntimePackLibraryDirectories" ItemName="_RuntimePackLibraryDirectory" />
<Output TaskParameter="NativeLibrariesToRemove" ItemName="_NativeLibraryToRemove" />
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@

<Target Name="_TouchAndroidLinkFlag"
AfterTargets="ILLink"
Condition=" '$(PublishTrimmed)' == 'true' and Exists('$(_LinkSemaphore)') "
Condition=" '$(PublishTrimmed)' == 'true' and '$(RunILLink)' != 'false' and Exists('$(_LinkSemaphore)') "
Inputs="$(_LinkSemaphore)"
Outputs="$(_AndroidLinkFlag)">
<!-- This file is an input for _RemoveRegisterAttribute -->
Expand Down
239 changes: 239 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using Microsoft.Android.Build.Tasks;

namespace Xamarin.Android.Tasks;

/// <summary>
/// Links NativeAOT-compiled object files into a shared library (.so) for Android.
/// Uses android-native-tools (our custom LLVM build) instead of the Android NDK.
/// </summary>
public class LinkNativeAotLibrary : AndroidTask
{
public override string TaskPrefix => "LNA";

[Required]
public string AndroidBinUtilsDirectory { get; set; } = "";

[Required]
public string IntermediateOutputPath { get; set; } = "";

/// <summary>
/// The main object file produced by ILC ($(NativeObject)).
/// </summary>
[Required]
public string NativeObject { get; set; } = "";

/// <summary>
/// Additional object files (e.g., generated assembler sources).
/// </summary>
public ITaskItem[] NativeObjectFiles { get; set; } = [];

/// <summary>
/// Static archives from ILC SDK and runtime packs.
/// </summary>
[Required]
public ITaskItem[] NativeArchives { get; set; } = [];

[Required]
public string OutputLibrary { get; set; } = "";

[Required]
public string RuntimeIdentifier { get; set; } = "";

[Required]
public ITaskItem[] RuntimePackLibraryDirectories { get; set; } = [];

public bool StripDebugSymbols { get; set; } = true;
public bool SaveDebugSymbols { get; set; } = true;

public override bool RunTask ()
{
string abi = GetAbiFromRuntimeIdentifier (RuntimeIdentifier);
string clangArch = GetClangArchFromRuntimeIdentifier (RuntimeIdentifier);

// Compute soname - Android requires a proper soname or it will refuse to load the library
string soname = Path.GetFileNameWithoutExtension (OutputLibrary);
if (soname.StartsWith ("lib", StringComparison.OrdinalIgnoreCase)) {
soname = soname.Substring (3);
}

// Find the sysroot directory from runtime pack library directories
string? sysrootDir = FindSysrootDirectory ();
if (sysrootDir == null) {
Log.LogError ("Could not find sysroot directory containing C++ runtime libraries in runtime pack");
return false;
}

var linker = new NativeLinker (Log, abi, soname, AndroidBinUtilsDirectory, IntermediateOutputPath, RuntimePackLibraryDirectories) {
StripDebugSymbols = StripDebugSymbols,
SaveDebugSymbols = SaveDebugSymbols,
AllowUndefinedSymbols = false,
UseNdkLibraries = false,
TargetsCLR = false, // NativeAOT uses its own runtime, not CoreCLR
UseSymbolic = true,
IsNativeAOT = true, // Enable NativeAOT-specific linker flags
};

List<ITaskItem> linkItems = OrganizeCommandLineItems (abi, sysrootDir, clangArch);
List<ITaskItem> linkStartFiles = GetCrtStartFiles (abi, sysrootDir);
List<ITaskItem> linkEndFiles = GetCrtEndFiles (abi, sysrootDir);

bool success = linker.Link (
CreateItemWithAbi (OutputLibrary, abi),
linkItems,
linkStartFiles,
linkEndFiles,
exportDynamicSymbols: null
);

if (!success) {
Log.LogError ($"Failed to link NativeAOT library: {OutputLibrary}");
}

return success;
}

string GetAbiFromRuntimeIdentifier (string rid)
{
return rid switch {
"android-arm64" => "arm64-v8a",
"android-x64" => "x86_64",
_ => throw new NotSupportedException ($"Unsupported RuntimeIdentifier for NativeAOT: {rid}")
};
}

string GetClangArchFromRuntimeIdentifier (string rid)
{
return rid switch {
"android-arm64" => "aarch64",
"android-x64" => "x86_64",
_ => throw new NotSupportedException ($"Unsupported RuntimeIdentifier for NativeAOT: {rid}")
};
}

/// <summary>
/// Finds the sysroot directory containing C++ runtime libraries.
/// </summary>
string? FindSysrootDirectory ()
{
foreach (var dir in RuntimePackLibraryDirectories) {
string libcppPath = Path.Combine (dir.ItemSpec, "libc++_static.a");
if (File.Exists (libcppPath)) {
return dir.ItemSpec;
}
}
return null;
}

/// <summary>
/// Get CRT start files (crtbegin_so.o).
/// </summary>
List<ITaskItem> GetCrtStartFiles (string abi, string sysrootDir)
{
var items = new List<ITaskItem> ();
string crtbegin = Path.Combine (sysrootDir, "crtbegin_so.o");
if (File.Exists (crtbegin)) {
items.Add (CreateItemWithAbi (crtbegin, abi));
} else {
Log.LogError ($"Required CRT file 'crtbegin_so.o' not found in {sysrootDir}. The NativeAOT runtime pack may be incomplete.");
}
return items;
}

/// <summary>
/// Get CRT end files (crtend_so.o).
/// </summary>
List<ITaskItem> GetCrtEndFiles (string abi, string sysrootDir)
{
var items = new List<ITaskItem> ();
string crtend = Path.Combine (sysrootDir, "crtend_so.o");
if (File.Exists (crtend)) {
items.Add (CreateItemWithAbi (crtend, abi));
} else {
Log.LogError ($"Required CRT file 'crtend_so.o' not found in {sysrootDir}. The NativeAOT runtime pack may be incomplete.");
}
return items;
}

/// <summary>
/// Organizes link items in the correct order for the native linker.
/// Order matters for static linking!
/// </summary>
List<ITaskItem> OrganizeCommandLineItems (string abi, string sysrootDir, string clangArch)
{
var items = new List<ITaskItem> ();

// First: ILC's main object file
items.Add (CreateItemWithAbi (NativeObject, abi));

// Then: additional object files (generated assembler sources)
foreach (ITaskItem objFile in NativeObjectFiles) {
items.Add (CreateItemWithAbi (objFile.ItemSpec, abi));
}

// Then: static archives from ILC SDK and runtime packs
foreach (ITaskItem archive in NativeArchives) {
var item = CreateItemWithAbi (archive.ItemSpec, abi);
// Check if this archive should be included with --whole-archive
string? wholeArchive = archive.GetMetadata (KnownMetadata.NativeLinkWholeArchive);
if (!wholeArchive.IsNullOrEmpty () && Boolean.Parse (wholeArchive)) {
item.SetMetadata (KnownMetadata.NativeLinkWholeArchive, "true");
}
items.Add (item);
}

// C++ standard library (required by NativeAOT runtime for std::nothrow, operator new/delete, etc.)
string libcppStatic = Path.Combine (sysrootDir, "libc++_static.a");
if (File.Exists (libcppStatic)) {
items.Add (CreateItemWithAbi (libcppStatic, abi));
} else {
Log.LogError ($"Required library 'libc++_static.a' not found in {sysrootDir}. The NativeAOT runtime pack may be incomplete.");
}

string libcppabi = Path.Combine (sysrootDir, "libc++abi.a");
if (File.Exists (libcppabi)) {
items.Add (CreateItemWithAbi (libcppabi, abi));
} else {
Log.LogError ($"Required library 'libc++abi.a' not found in {sysrootDir}. The NativeAOT runtime pack may be incomplete.");
}

// Unwinding support
string libunwind = Path.Combine (sysrootDir, "libunwind.a");
if (File.Exists (libunwind)) {
items.Add (CreateItemWithAbi (libunwind, abi));
} else {
Log.LogError ($"Required library 'libunwind.a' not found in {sysrootDir}. The NativeAOT runtime pack may be incomplete.");
}

// Compiler runtime builtins (required for atomic intrinsics and TLS emulation)
string libclangBuiltins = Path.Combine (sysrootDir, $"libclang_rt.builtins-{clangArch}-android.a");
if (File.Exists (libclangBuiltins)) {
items.Add (CreateItemWithAbi (libclangBuiltins, abi));
} else {
Log.LogError ($"Required library 'libclang_rt.builtins-{clangArch}-android.a' not found in {sysrootDir}. The NativeAOT runtime pack may be incomplete.");
}

// Add required system libraries (linked dynamically)
items.Add (NativeLinker.MakeLibraryItem ("log", abi)); // Android logging
items.Add (NativeLinker.MakeLibraryItem ("z", abi)); // zlib compression
items.Add (NativeLinker.MakeLibraryItem ("m", abi)); // math library
items.Add (NativeLinker.MakeLibraryItem ("dl", abi)); // dynamic linking
items.Add (NativeLinker.MakeLibraryItem ("c", abi)); // C library (must be last)

return items;
}

ITaskItem CreateItemWithAbi (string path, string abi)
{
var item = new TaskItem (path);
item.SetMetadata (KnownMetadata.Abi, abi);
return item;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"Size": 3124
},
"classes.dex": {
"Size": 24224
"Size": 25320
},
"lib/arm64-v8a/libUnnamedProject.so": {
"Size": 4968680
"Size": 5424528
},
"META-INF/BNDLTOOL.RSA": {
"Size": 1211
"Size": 1213
},
"META-INF/BNDLTOOL.SF": {
"Size": 1211
Expand Down Expand Up @@ -44,5 +44,5 @@
"Size": 1904
}
},
"PackageSize": 2094050
"PackageSize": 2225122
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"Size": 6740
},
"classes.dex": {
"Size": 9136980
"Size": 9132020
},
"kotlin/annotation/annotation.kotlin_builtins": {
"Size": 928
Expand All @@ -29,7 +29,7 @@
"Size": 2396
},
"lib/arm64-v8a/libUnnamedProject.so": {
"Size": 21592848
"Size": 22047616
},
"META-INF/androidx.activity_activity.version": {
"Size": 6
Expand Down Expand Up @@ -182,7 +182,7 @@
"Size": 6
},
"META-INF/BNDLTOOL.RSA": {
"Size": 1223
"Size": 1213
},
"META-INF/BNDLTOOL.SF": {
"Size": 89178
Expand Down Expand Up @@ -2252,5 +2252,5 @@
"Size": 812848
}
},
"PackageSize": 12521545
"PackageSize": 12652617
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ 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}");
// Not all runtime packs include the DSO stub (e.g. NativeAOT packs don't
// need DSO wrapping), so just skip directories that don't participate.
log.LogDebugMessage ($"Skipping runtime pack directory '{packLibDir.ItemSpec}': no DSO stub found");
continue;
}

AndroidTargetArch arch = MonoAndroidHelper.RidToArch (packRID);
Expand Down
Loading
Loading