diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets
index aa145ccb839..169d3ffaecd 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets
@@ -78,13 +78,6 @@
EnableNativeRuntimeLinking="$(_AndroidEnableNativeRuntimeLinking)">
-
-
-
+
+
+
+ <_MarshalMethodsAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index bf709baf4c6..ae00afa57e8 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -244,12 +244,10 @@ internal static Dictionary MaybeGetArchAssemblies (Dictionary
return (false, null);
}
- MarshalMethodsCollection? marshalMethodsCollection = null;
-
- if (useMarshalMethods)
- marshalMethodsCollection = MarshalMethodsCollection.FromAssemblies (arch, assemblies.Values.ToList (), resolver, Log);
-
- return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, marshalMethodsCollection));
+ // Marshal method classification is now done in the inner build by
+ // RewriteMarshalMethods, so we never classify here. The NativeCodeGenState
+ // will have a null Classifier; downstream tasks must handle that.
+ return (true, new NativeCodeGenState (arch, tdCache, resolver, allJavaTypes, javaTypesForJCW, classifier: null));
}
(List allJavaTypes, List javaTypesForJCW) ScanForJavaTypes (XAAssemblyResolver res, TypeDefinitionCache cache, Dictionary assemblies, Dictionary userAssemblies, bool useMarshalMethods)
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs
index a0134611a69..0c63d6aaa1e 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeMarshalMethodSources.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
using Xamarin.Android.Tools;
@@ -10,29 +9,15 @@
namespace Xamarin.Android.Tasks;
///
-/// MSBuild task that generates native LLVM assembly source files containing marshal methods and
-/// optional P/Invoke preservation code. This task creates the final native code that bridges
-/// between .NET and Java/JNI, using the marshal method classifications and rewritten assemblies
-/// from previous pipeline stages.
+/// MSBuild task that generates P/Invoke preservation LLVM IR source files when native
+/// runtime linking is enabled. Creates pinvoke_preserve.{abi}.ll files for each
+/// supported ABI.
///
///
-/// This task is responsible for the final code generation phase of the marshal methods pipeline:
-///
-/// 1. **Marshal Methods Generation**: Creates LLVM IR code for native marshal methods that can
-/// be called directly from Java/JNI without dynamic registration overhead.
-///
-/// 2. **P/Invoke Preservation** (when EnableNativeRuntimeLinking=true): Generates additional
-/// code to preserve P/Invoke methods that would otherwise be removed by native linking.
-///
-/// 3. **Runtime-Specific Code**: Adapts the generated code for the target runtime (MonoVM or CoreCLR),
-/// handling differences in runtime linking and method resolution.
-///
-/// 4. **Architecture Support**: Generates separate code files for each supported Android ABI
-/// (arm64-v8a, armeabi-v7a, x86_64, x86).
-///
-/// The task generates LLVM IR (.ll) files that are later compiled to native assembly by the
-/// Android NDK toolchain. Even when marshal methods are disabled, empty files are generated
-/// to maintain build consistency.
+/// Marshal method .ll generation is handled entirely by the inner build's
+/// task. This task only handles P/Invoke preservation:
+/// when is true, it generates additional LLVM IR
+/// code that prevents the native linker from removing required P/Invoke entry points.
///
public class GenerateNativeMarshalMethodSources : AndroidTask
{
@@ -41,21 +26,9 @@ public class GenerateNativeMarshalMethodSources : AndroidTask
///
public override string TaskPrefix => "GNM";
- ///
- /// Gets or sets whether to generate managed marshal methods lookup tables.
- /// When enabled, creates runtime data structures for efficient marshal method resolution.
- ///
- public bool EnableManagedMarshalMethodsLookup { get; set; }
-
- ///
- /// Gets or sets whether marshal methods generation is enabled.
- /// When false, generates empty placeholder files to maintain build consistency.
- ///
- public bool EnableMarshalMethods { get; set; }
-
///
/// Gets or sets whether native runtime linking is enabled.
- /// When true, generates additional P/Invoke preservation code to prevent
+ /// When true, generates P/Invoke preservation code to prevent
/// native linker from removing required methods.
///
public bool EnableNativeRuntimeLinking { get; set; }
@@ -67,8 +40,8 @@ public class GenerateNativeMarshalMethodSources : AndroidTask
public ITaskItem[] MonoComponents { get; set; } = [];
///
- /// Gets or sets the output directory for environment files.
- /// Generated LLVM IR files are written to this directory.
+ /// Gets or sets the output directory for generated files.
+ /// P/Invoke preservation LLVM IR files are written to this directory.
///
[Required]
public string EnvironmentOutputDirectory { get; set; } = "";
@@ -80,26 +53,6 @@ public class GenerateNativeMarshalMethodSources : AndroidTask
[Required]
public string IntermediateOutputDirectory { get; set; } = "";
- ///
- /// Gets or sets the resolved assemblies to process for marshal method generation.
- /// These assemblies have been processed by previous pipeline stages.
- ///
- [Required]
- public ITaskItem [] ResolvedAssemblies { get; set; } = [];
-
- ///
- /// Gets or sets the target Android runtime (MonoVM or CoreCLR).
- /// Determines which runtime-specific code generator to use.
- ///
- [Required]
- public string AndroidRuntime { get; set; } = "";
-
- ///
- /// Gets or sets the satellite assemblies containing localized resources.
- /// These are included in assembly counting and naming for native code generation.
- ///
- public ITaskItem [] SatelliteAssemblies { get; set; } = [];
-
///
/// Gets or sets the list of supported Android ABIs to generate code for.
/// Common values include arm64-v8a, armeabi-v7a, x86_64, and x86.
@@ -107,42 +60,30 @@ public class GenerateNativeMarshalMethodSources : AndroidTask
[Required]
public string [] SupportedAbis { get; set; } = [];
- // Parsed Android runtime type
- AndroidRuntime androidRuntime;
-
///
- /// Executes the native marshal method source generation task.
- /// Coordinates the generation of LLVM IR files for all supported Android ABIs.
+ /// Executes the P/Invoke preservation source generation task.
///
///
/// true if the task completed successfully; false if errors occurred during processing.
///
///
/// The execution flow is:
- ///
- /// 1. Parse the Android runtime type (MonoVM or CoreCLR)
- /// 2. Retrieve native code generation state from previous pipeline stages (if marshal methods enabled)
- /// 3. Generate LLVM IR files for each supported ABI
- /// 4. Handle both marshal methods and P/Invoke preservation code as needed
- ///
- /// The native code generation state is removed from the cache after retrieval to ensure
- /// it's not accidentally reused by subsequent build tasks.
+ ///
+ /// 1. Unregister (clean up) the native code generation state from the build engine cache
+ /// 2. If native runtime linking is enabled, generate P/Invoke preservation LLVM IR for each ABI
+ ///
+ /// The native code generation state is always unregistered to prevent accidental reuse,
+ /// even if P/Invoke preservation is not needed.
///
public override bool RunTask ()
{
- NativeCodeGenStateCollection? nativeCodeGenStates = null;
- androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
+ // Always unregister the NativeCodeGenStateCollection to clean up.
+ // P/Invoke preservation needs the state when native runtime linking is enabled.
+ var nativeCodeGenStates = BuildEngine4.UnregisterTaskObjectAssemblyLocal (
+ MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
+ RegisteredTaskObjectLifetime.Build
+ );
- // Retrieve native code generation state only if we need it
- if (EnableMarshalMethods || EnableNativeRuntimeLinking) {
- // Retrieve the stored NativeCodeGenStateCollection (and remove it from the cache)
- nativeCodeGenStates = BuildEngine4.UnregisterTaskObjectAssemblyLocal (
- MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateObjectRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
- RegisteredTaskObjectLifetime.Build
- );
- }
-
- // Generate native code for each supported ABI
foreach (var abi in SupportedAbis)
Generate (nativeCodeGenStates, abi);
@@ -150,200 +91,49 @@ public override bool RunTask ()
}
///
- /// Generates native LLVM IR source files for a specific Android ABI.
- /// Creates both marshal methods and optional P/Invoke preservation code.
+ /// Generates P/Invoke preservation LLVM IR source files for a specific Android ABI.
///
///
/// Collection of native code generation states from previous pipeline stages.
- /// May be null if marshal methods are disabled.
+ /// Required when native runtime linking is enabled.
///
/// The target Android ABI to generate code for (e.g., "arm64-v8a").
///
- /// This method handles the complete code generation workflow:
- ///
- /// 1. **Setup**: Determines target architecture, file paths, and assembly information
- /// 2. **Generator Creation**: Creates runtime-specific code generators (MonoVM or CoreCLR)
- /// 3. **P/Invoke Preservation** (optional): Generates code to preserve P/Invoke methods
- /// 4. **Marshal Methods**: Generates the main marshal methods LLVM IR code
- /// 5. **File Output**: Writes generated code to disk with proper error handling
- ///
- /// The generated files are:
- /// - `marshal_methods.{abi}.ll`: Main marshal methods LLVM IR
- /// - `pinvoke_preserve.{abi}.ll`: P/Invoke preservation code (when native linking enabled)
- ///
- /// Both generators construct an LLVM IR module and then generate the actual code,
- /// with proper stream management and error recovery in case of partial writes.
+ /// When is false, this method returns immediately.
+ /// Otherwise it generates pinvoke_preserve.{abi}.ll containing references to
+ /// P/Invoke entry points that must survive native linking.
///
void Generate (NativeCodeGenStateCollection? nativeCodeGenStates, string abi)
{
- // Setup target information and file paths
- var targetAbi = abi.ToLowerInvariant ();
- var targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
- var marshalMethodsBaseAsmFilePath = Path.Combine (EnvironmentOutputDirectory, $"marshal_methods.{targetAbi}");
- var pinvokePreserveBaseAsmFilePath = EnableNativeRuntimeLinking ? Path.Combine (EnvironmentOutputDirectory, $"pinvoke_preserve.{targetAbi}") : null;
- var marshalMethodsLlFilePath = $"{marshalMethodsBaseAsmFilePath}.ll";
- var pinvokePreserveLlFilePath = pinvokePreserveBaseAsmFilePath != null ? $"{pinvokePreserveBaseAsmFilePath}.ll" : null;
- var (assemblyCount, uniqueAssemblyNames) = GetAssemblyCountAndUniqueNames ();
-
- // Create the appropriate runtime-specific generator
- MarshalMethodsNativeAssemblyGenerator marshalMethodsAsmGen = androidRuntime switch {
- Tasks.AndroidRuntime.MonoVM => MakeMonoGenerator (),
- Tasks.AndroidRuntime.CoreCLR => MakeCoreCLRGenerator (),
- _ => throw new NotSupportedException ($"Internal error: unsupported runtime type '{androidRuntime}'")
- };
-
- // Generate P/Invoke preservation code if native linking is enabled
- bool fileFullyWritten;
- if (EnableNativeRuntimeLinking) {
- var pinvokePreserveGen = new PreservePinvokesNativeAssemblyGenerator (Log, EnsureCodeGenState (nativeCodeGenStates, targetArch), MonoComponents);
- LLVMIR.LlvmIrModule pinvokePreserveModule = pinvokePreserveGen.Construct ();
- using var pinvokePreserveWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
- fileFullyWritten = false;
- try {
- pinvokePreserveGen.Generate (pinvokePreserveModule, targetArch, pinvokePreserveWriter, pinvokePreserveLlFilePath!);
- pinvokePreserveWriter.Flush ();
- Files.CopyIfStreamChanged (pinvokePreserveWriter.BaseStream, pinvokePreserveLlFilePath!);
- fileFullyWritten = true;
- } finally {
- // Log partial contents for debugging if generation failed
- if (!fileFullyWritten) {
- MonoAndroidHelper.LogTextStreamContents (Log, $"Partial contents of file '{pinvokePreserveLlFilePath}'", pinvokePreserveWriter.BaseStream);
- }
- }
+ if (!EnableNativeRuntimeLinking) {
+ return;
}
- // Generate marshal methods code
- var marshalMethodsModule = marshalMethodsAsmGen.Construct ();
- using var marshalMethodsWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
+ // Generate P/Invoke preservation code
+ var targetAbi = abi.ToLowerInvariant ();
+ var targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
+ var pinvokePreserveLlFilePath = Path.Combine (EnvironmentOutputDirectory, $"pinvoke_preserve.{targetAbi}.ll");
- fileFullyWritten = false;
+ var pinvokePreserveGen = new PreservePinvokesNativeAssemblyGenerator (Log, EnsureCodeGenState (nativeCodeGenStates, targetArch), MonoComponents);
+ LLVMIR.LlvmIrModule pinvokePreserveModule = pinvokePreserveGen.Construct ();
+ using var pinvokePreserveWriter = MemoryStreamPool.Shared.CreateStreamWriter ();
+ bool fileFullyWritten = false;
try {
- marshalMethodsAsmGen.Generate (marshalMethodsModule, targetArch, marshalMethodsWriter, marshalMethodsLlFilePath);
- marshalMethodsWriter.Flush ();
- Files.CopyIfStreamChanged (marshalMethodsWriter.BaseStream, marshalMethodsLlFilePath);
+ pinvokePreserveGen.Generate (pinvokePreserveModule, targetArch, pinvokePreserveWriter, pinvokePreserveLlFilePath);
+ pinvokePreserveWriter.Flush ();
+ Files.CopyIfStreamChanged (pinvokePreserveWriter.BaseStream, pinvokePreserveLlFilePath);
fileFullyWritten = true;
} finally {
- // Log partial contents for debugging if generation failed
if (!fileFullyWritten) {
- MonoAndroidHelper.LogTextStreamContents (Log, $"Partial contents of file '{marshalMethodsLlFilePath}'", marshalMethodsWriter.BaseStream);
- }
- }
-
- ///
- /// Creates a MonoVM-specific marshal methods generator.
- /// Handles both enabled and disabled marshal methods scenarios.
- ///
- /// A configured MonoVM marshal methods generator.
- MarshalMethodsNativeAssemblyGenerator MakeMonoGenerator ()
- {
- if (EnableMarshalMethods) {
- return new MarshalMethodsNativeAssemblyGeneratorMonoVM (
- Log,
- assemblyCount,
- uniqueAssemblyNames,
- EnsureCodeGenState (nativeCodeGenStates, targetArch),
- EnableManagedMarshalMethodsLookup
- );
- }
-
- // Generate empty/minimal code when marshal methods are disabled
- return new MarshalMethodsNativeAssemblyGeneratorMonoVM (
- Log,
- targetArch,
- assemblyCount,
- uniqueAssemblyNames
- );
- }
-
- ///
- /// Creates a CoreCLR-specific marshal methods generator.
- /// Handles both enabled and disabled marshal methods scenarios.
- ///
- /// A configured CoreCLR marshal methods generator.
- MarshalMethodsNativeAssemblyGenerator MakeCoreCLRGenerator ()
- {
- if (EnableMarshalMethods) {
- return new MarshalMethodsNativeAssemblyGeneratorCoreCLR (
- Log,
- uniqueAssemblyNames,
- EnsureCodeGenState (nativeCodeGenStates, targetArch),
- EnableManagedMarshalMethodsLookup
- );
- }
-
- // Generate empty/minimal code when marshal methods are disabled
- return new MarshalMethodsNativeAssemblyGeneratorCoreCLR (
- Log,
- targetArch,
- uniqueAssemblyNames
- );
- }
- }
-
- ///
- /// Counts the total number of assemblies and collects unique assembly names
- /// from both resolved assemblies and satellite assemblies.
- ///
- ///
- /// A tuple containing:
- /// - assemblyCount: The total number of unique assemblies across all architectures
- /// - uniqueAssemblyNames: A set of unique assembly names including culture information
- ///
- ///
- /// This method processes both main assemblies and satellite assemblies (for localization).
- /// For satellite assemblies, the culture name is prepended to create unique identifiers
- /// (e.g., "en-US/MyApp.resources.dll"). This information is used by the native code
- /// generators to create appropriate lookup structures and assembly metadata.
- ///
- (int assemblyCount, HashSet uniqueAssemblyNames) GetAssemblyCountAndUniqueNames ()
- {
- var assemblyCount = 0;
- var archAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase);
- var uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase);
-
- // Process both main assemblies and satellite assemblies
- foreach (var assembly in SatelliteAssemblies.Concat (ResolvedAssemblies)) {
- var culture = MonoAndroidHelper.GetAssemblyCulture (assembly);
- var fileName = Path.GetFileName (assembly.ItemSpec);
- string assemblyName;
-
- // Include culture information for satellite assemblies
- if (culture.IsNullOrEmpty ()) {
- assemblyName = fileName;
- } else {
- assemblyName = $"{culture}/{fileName}";
- }
-
- // Track all unique assembly names across architectures
- uniqueAssemblyNames.Add (assemblyName);
-
- // Count unique assemblies per architecture to avoid duplicates
- if (!archAssemblyNames.Contains (assemblyName)) {
- assemblyCount++;
- archAssemblyNames.Add (assemblyName);
+ MonoAndroidHelper.LogTextStreamContents (Log, $"Partial contents of file '{pinvokePreserveLlFilePath}'", pinvokePreserveWriter.BaseStream);
}
}
-
- return (assemblyCount, uniqueAssemblyNames);
}
///
/// Retrieves the native code generation state for a specific target architecture.
/// Validates that the required state exists and throws an exception if missing.
///
- ///
- /// The collection of native code generation states from previous pipeline stages.
- ///
- /// The target architecture to retrieve state for.
- /// The native code generation state for the specified architecture.
- ///
- /// Thrown when the state collection is null or doesn't contain state for the target architecture.
- ///
- ///
- /// This method ensures that the required native code generation state is available
- /// before attempting to generate marshal methods code. The state contains marshal method
- /// classifications, assembly information, and other data needed for code generation.
- ///
NativeCodeGenStateObject EnsureCodeGenState (NativeCodeGenStateCollection? nativeCodeGenStates, AndroidTargetArch targetArch)
{
if (nativeCodeGenStates is null || !nativeCodeGenStates.States.TryGetValue (targetArch, out NativeCodeGenStateObject? state)) {
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
index 70a6a60f5a9..0db0d4ac7a0 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs
@@ -53,8 +53,6 @@ public class GenerateTypeMappings : AndroidTask
public override bool RunTask ()
{
- var useMarshalMethods = !Debug && EnableMarshalMethods;
-
androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
// NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
@@ -62,14 +60,15 @@ public override bool RunTask ()
return !Log.HasLoggedErrors;
}
- // If using marshal methods, we cannot use the .typemap.xml files currently because
- // the type token ids were changed by the marshal method rewriter after we wrote the .xml files.
- if (!useMarshalMethods)
- GenerateAllTypeMappings ();
+ // Always use the .typemap.xml file path. When marshal methods are enabled,
+ // the assemblies returned from the inner build are already rewritten, so the
+ // .typemap.xml files generated by _AfterILLinkAdditionalSteps have correct
+ // post-rewrite tokens.
+ GenerateAllTypeMappings ();
- // Generate typemaps from the native code generator state (produced by the marshal method rewriter)
- if (RunCheckedBuild || useMarshalMethods)
- GenerateAllTypeMappingsFromNativeState (useMarshalMethods);
+ // In a checked build, also generate from native state for comparison
+ if (RunCheckedBuild)
+ GenerateAllTypeMappingsFromNativeState ();
return !Log.HasLoggedErrors;
}
@@ -106,7 +105,7 @@ void GenerateTypeMap (AndroidTargetArch arch, List assemblies)
AddOutputTypeMaps (tmg, state.TargetArch);
}
- void GenerateAllTypeMappingsFromNativeState (bool useMarshalMethods)
+ void GenerateAllTypeMappingsFromNativeState ()
{
// Retrieve the stored NativeCodeGenState
var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal> (
@@ -114,23 +113,13 @@ void GenerateAllTypeMappingsFromNativeState (bool useMarshalMethods)
RegisteredTaskObjectLifetime.Build
);
- NativeCodeGenState? templateCodeGenState = null;
-
foreach (var kvp in nativeCodeGenStates) {
NativeCodeGenState state = kvp.Value;
- templateCodeGenState = state;
- GenerateTypeMapFromNativeState (state, useMarshalMethods);
+ GenerateTypeMapFromNativeState (state);
}
-
- if (templateCodeGenState is null)
- throw new InvalidOperationException ($"Internal error: no native code generator state defined");
-
- // Set for use by task later
- if (useMarshalMethods)
- NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent = templateCodeGenState.JniAddNativeMethodRegistrationAttributePresent;
}
- void GenerateTypeMapFromNativeState (NativeCodeGenState state, bool useMarshalMethods)
+ void GenerateTypeMapFromNativeState (NativeCodeGenState state)
{
if (androidRuntime == Xamarin.Android.Tasks.AndroidRuntime.NativeAOT) {
// NativeAOT typemaps are generated in `Microsoft.Android.Sdk.ILLink.TypeMappingStep`
@@ -144,7 +133,7 @@ void GenerateTypeMapFromNativeState (NativeCodeGenState state, bool useMarshalMe
state = new NativeCodeGenState (state.TargetArch, new TypeDefinitionCache (), state.Resolver, [], [], state.Classifier);
}
- var tmg = new TypeMapGenerator (Log, new NativeCodeGenStateAdapter (state), androidRuntime) { RunCheckedBuild = RunCheckedBuild && !useMarshalMethods };
+ var tmg = new TypeMapGenerator (Log, new NativeCodeGenStateAdapter (state), androidRuntime) { RunCheckedBuild = RunCheckedBuild };
tmg.Generate (Debug, SkipJniAddNativeMethodRegistrationAttributeScan, TypemapOutputDirectory);
AddOutputTypeMaps (tmg, state.TargetArch);
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs
index e8af07e4737..bf15873404c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/RewriteMarshalMethods.cs
@@ -1,162 +1,334 @@
#nullable enable
-using System.Collections.Concurrent;
+using System;
+using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
using Xamarin.Android.Tools;
namespace Xamarin.Android.Tasks;
///
-/// MSBuild task that rewrites .NET assemblies to use marshal methods instead of dynamic JNI registration.
-/// This task modifies method implementations to use efficient native callbacks with [UnmanagedCallersOnly]
-/// attributes, significantly improving startup performance and reducing runtime overhead for Android applications.
+/// MSBuild task that runs in the inner (per-RID) build to generate the
+/// marshal_methods.{abi}.ll LLVM IR file.
+///
+/// When marshal methods are enabled (Release + MonoVM), the task classifies
+/// marshal methods, rewrites assemblies (adds [UnmanagedCallersOnly] wrappers,
+/// removes connectors), and generates a full .ll with native marshal method
+/// functions. Rewritten assemblies are written to a separate per-RID output
+/// directory (), never in-place,
+/// so that parallel inner builds don't conflict even when the input assemblies
+/// point to a shared location (e.g. the NuGet runtime pack when PublishTrimmed
+/// is false). The output items () allow the
+/// target to update @(ResolvedFileToPublish) so downstream processing
+/// uses the rewritten copies.
+///
+/// When marshal methods are disabled (Debug, or Release without marshal
+/// methods), the task generates an empty/minimal .ll containing only the
+/// structural scaffolding the native runtime always links against.
+///
+/// Runs AfterTargets="_PostTrimmingPipeline" (which is AfterTargets="ILLink")
+/// so that trimmed assemblies are available for rewriting when trimming is
+/// active. MSBuild fires AfterTargets hooks even when the referenced target
+/// is condition-skipped, so this target also runs in untrimmed builds.
///
-///
-/// This task operates on the marshal method classifications produced by earlier pipeline stages and:
-///
-/// 1. Retrieves marshal method classifications from the build pipeline state
-/// 2. Parses environment files to determine exception transition behavior
-/// 3. Rewrites assemblies to replace dynamic registration with static marshal methods
-/// 4. Optionally builds managed lookup tables for runtime marshal method resolution
-/// 5. Reports statistics on marshal method generation and any fallback to dynamic registration
-///
-/// The rewriting process creates native callback wrappers for methods that have non-blittable
-/// parameters or return types, ensuring compatibility with the [UnmanagedCallersOnly] attribute
-/// while maintaining proper marshaling semantics.
-///
public class RewriteMarshalMethods : AndroidTask
{
+ public override string TaskPrefix => "RMM";
+
///
- /// Gets the task prefix used for logging and error messages.
+ /// The assemblies to process (from @(ResolvedFileToPublish) filtered to .dll).
///
- public override string TaskPrefix => "RMM";
+ [Required]
+ public ITaskItem [] Assemblies { get; set; } = [];
+
+ ///
+ /// The Android runtime type (MonoVM or CoreCLR). Determines which LLVM IR generator
+ /// to use for the marshal_methods.{abi}.ll file.
+ ///
+ [Required]
+ public string AndroidRuntime { get; set; } = "";
///
- /// Gets or sets whether to enable managed marshal methods lookup tables.
- /// When enabled, generates runtime lookup structures that allow dynamic resolution
- /// of marshal methods without string comparisons, improving runtime performance.
+ /// Whether to enable managed marshal methods lookup tables.
///
public bool EnableManagedMarshalMethodsLookup { get; set; }
///
- /// Gets or sets the environment files to parse for configuration settings.
- /// These files may contain settings like XA_BROKEN_EXCEPTION_TRANSITIONS that
- /// affect how marshal method wrappers are generated.
+ /// Whether marshal methods are enabled. When false, the task skips classification
+ /// and rewriting and generates an empty/minimal .ll file.
+ ///
+ public bool EnableMarshalMethods { get; set; }
+
+ ///
+ /// Environment files to parse for configuration (e.g. XA_BROKEN_EXCEPTION_TRANSITIONS).
+ /// Only used when marshal methods are enabled.
///
public ITaskItem [] Environments { get; set; } = [];
///
- /// Gets or sets the intermediate output directory path. Required for retrieving
- /// build state objects that contain marshal method classifications.
+ /// Directory where the marshal_methods.{abi}.ll file is written.
+ /// Typically $(_OuterIntermediateOutputPath)android so the outer build's
+ /// _CompileNativeAssemblySources can compile it.
///
[Required]
- public string IntermediateOutputDirectory { get; set; } = "";
+ public string MarshalMethodsOutputDirectory { get; set; } = "";
///
- /// Executes the marshal method rewriting task. This is the main entry point that
- /// coordinates the entire assembly rewriting process across all target architectures.
+ /// Per-RID directory where rewritten assemblies are written. Rewritten
+ /// assemblies are always written to this directory (never in-place) so that
+ /// parallel inner builds for different RIDs don't conflict, even when the
+ /// input assemblies reside in a shared location such as the NuGet runtime pack.
+ /// Only used when is true.
///
- ///
- /// true if the task completed successfully; false if errors occurred during processing.
- ///
- ///
- /// The execution flow is:
- ///
- /// 1. Retrieve native code generation state from previous pipeline stages
- /// 2. Parse environment files for configuration (e.g., broken exception transitions)
- /// 3. For each target architecture:
- /// - Rewrite assemblies to use marshal methods
- /// - Add special case methods (e.g., TypeManager methods)
- /// - Optionally build managed lookup tables
- /// 4. Report statistics on marshal method generation
- /// 5. Log warnings for methods that must fall back to dynamic registration
- ///
- /// The task handles the ordering dependency between special case methods and managed
- /// lookup tables - special cases must be added first so they appear in the lookup tables.
- ///
+ [Required]
+ public string RewrittenAssembliesOutputDirectory { get; set; } = "";
+
+ ///
+ /// The RuntimeIdentifier for this inner build (e.g. android-arm64).
+ /// Converted to an ABI and target architecture internally.
+ ///
+ [Required]
+ public string RuntimeIdentifier { get; set; } = "";
+
+ ///
+ /// Output: assemblies (and PDBs) that were rewritten to
+ /// . Each item carries all
+ /// metadata from the original input assembly plus OriginalItemSpec
+ /// pointing to the original path. The calling target uses these to replace
+ /// the original items in @(ResolvedFileToPublish) so downstream
+ /// processing picks up the rewritten copies.
+ ///
+ [Output]
+ public ITaskItem []? RewrittenAssemblies { get; set; }
+
public override bool RunTask ()
{
- // Retrieve the stored NativeCodeGenState from the build pipeline
- // This contains marshal method classifications from earlier stages
- var nativeCodeGenStates = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal> (
- MonoAndroidHelper.GetProjectBuildSpecificTaskObjectKey (GenerateJavaStubs.NativeCodeGenStateRegisterTaskKey, WorkingDirectory, IntermediateOutputDirectory),
- RegisteredTaskObjectLifetime.Build
- );
-
- // Parse environment files to determine configuration settings
- // We need to parse the environment files supplied by the user to see if they want to use broken exception transitions. This information is needed
- // in order to properly generate wrapper methods in the marshal methods assembly rewriter.
- // We don't care about those generated by us, since they won't contain the `XA_BROKEN_EXCEPTION_TRANSITIONS` variable we look for.
+ var androidRuntime = MonoAndroidHelper.ParseAndroidRuntime (AndroidRuntime);
+
+ string abi = MonoAndroidHelper.RidToAbi (RuntimeIdentifier);
+ var targetArch = MonoAndroidHelper.AbiToTargetArch (abi);
+
+ // The inner build's @(ResolvedFileToPublish) items don't have %(Abi) metadata yet —
+ // that's normally stamped later by ProcessAssemblies in the outer build. Downstream
+ // code (XAJavaTypeScanner) reads %(Abi) from each ITaskItem, so we need to set it here.
+ EnsureAbiMetadata (abi);
+
+ if (EnableMarshalMethods) {
+ ProcessMarshalMethods (targetArch, abi, androidRuntime);
+ } else {
+ GenerateEmptyLlvmIr (targetArch, abi, androidRuntime);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ ///
+ /// Marshal methods enabled path: classify, rewrite assemblies to output directory, generate full .ll.
+ ///
+ void ProcessMarshalMethods (AndroidTargetArch targetArch, string abi, AndroidRuntime androidRuntime)
+ {
+ // Parse environment files for configuration (e.g. broken exception transitions)
var environmentParser = new EnvironmentFilesParser ();
bool brokenExceptionTransitionsEnabled = environmentParser.AreBrokenExceptionTransitionsEnabled (Environments);
- // Process each target architecture
- foreach (var kvp in nativeCodeGenStates) {
- NativeCodeGenState state = kvp.Value;
+ // Step 1: Open assemblies with Cecil and classify marshal methods
+ var assemblyDict = new Dictionary (StringComparer.OrdinalIgnoreCase);
+ foreach (var item in Assemblies) {
+ var name = Path.GetFileNameWithoutExtension (item.ItemSpec);
+ assemblyDict [name] = item;
+ }
+ var assemblyItems = assemblyDict.Values.ToList ();
- if (state.Classifier is null) {
- Log.LogError ("state.Classifier cannot be null if marshal methods are enabled");
- return false;
- }
+ XAAssemblyResolver resolver = MonoAndroidHelper.MakeResolver (Log, useMarshalMethods: true, targetArch, assemblyDict);
- // Handle the ordering dependency between special case methods and managed lookup tables
- if (!EnableManagedMarshalMethodsLookup) {
- // Standard path: rewrite first, then add special cases
- RewriteMethods (state, brokenExceptionTransitionsEnabled);
- state.Classifier.AddSpecialCaseMethods ();
- } else {
- // Managed lookup path: add special cases first so they appear in lookup tables
- // We need to run `AddSpecialCaseMethods` before `RewriteMarshalMethods` so that we can see the special case
- // methods (such as TypeManager.n_Activate_mm) when generating the managed lookup tables.
- state.Classifier.AddSpecialCaseMethods ();
- state.ManagedMarshalMethodsLookupInfo = new ManagedMarshalMethodsLookupInfo (Log);
- RewriteMethods (state, brokenExceptionTransitionsEnabled);
- }
+ MarshalMethodsCollection classifier;
+ try {
+ classifier = MarshalMethodsCollection.FromAssemblies (targetArch, assemblyItems, resolver, Log);
+ } catch (Exception ex) {
+ Log.LogError ($"[{targetArch}] Failed to classify marshal methods: {ex.Message}");
+ Log.LogDebugMessage (ex.ToString ());
+ return;
+ }
+
+ // Step 2: Rewrite assemblies to the per-RID output directory
+ HashSet rewrittenOriginalPaths;
+ if (!EnableManagedMarshalMethodsLookup) {
+ rewrittenOriginalPaths = RewriteAssemblies (targetArch, classifier, resolver, brokenExceptionTransitionsEnabled);
+ classifier.AddSpecialCaseMethods ();
+ } else {
+ classifier.AddSpecialCaseMethods ();
+ var lookupInfo = new ManagedMarshalMethodsLookupInfo (Log);
+ rewrittenOriginalPaths = RewriteAssemblies (targetArch, classifier, resolver, brokenExceptionTransitionsEnabled, lookupInfo);
+ }
+
+ ReportStatistics (targetArch, classifier);
- // Report statistics on marshal method generation
- Log.LogDebugMessage ($"[{state.TargetArch}] Number of generated marshal methods: {state.Classifier.MarshalMethods.Count}");
- if (state.Classifier.DynamicallyRegisteredMarshalMethods.Count > 0) {
- Log.LogWarning ($"[{state.TargetArch}] Number of methods in the project that will be registered dynamically: {state.Classifier.DynamicallyRegisteredMarshalMethods.Count}");
+ // Step 3: Build output items for rewritten assemblies (DLLs and PDBs)
+ BuildRewrittenAssembliesOutput (rewrittenOriginalPaths);
+
+ // Step 4: Build NativeCodeGenStateObject and generate .ll
+ var codeGenState = MarshalMethodCecilAdapter.CreateNativeCodeGenStateObjectFromClassifier (targetArch, classifier);
+ GenerateLlvmIr (targetArch, abi, androidRuntime, codeGenState);
+
+ // Step 5: Dispose Cecil resolvers
+ resolver.Dispose ();
+ }
+
+ ///
+ /// Marshal methods disabled path: generate empty/minimal .ll with structural scaffolding only.
+ ///
+ void GenerateEmptyLlvmIr (AndroidTargetArch targetArch, string abi, AndroidRuntime androidRuntime)
+ {
+ var emptyCodeGenState = new NativeCodeGenStateObject {
+ TargetArch = targetArch,
+ };
+ GenerateLlvmIr (targetArch, abi, androidRuntime, emptyCodeGenState);
+ }
+
+ HashSet RewriteAssemblies (AndroidTargetArch targetArch, MarshalMethodsCollection classifier, XAAssemblyResolver resolver, bool brokenExceptionTransitionsEnabled, ManagedMarshalMethodsLookupInfo? lookupInfo = null)
+ {
+ var rewriter = new MarshalMethodsAssemblyRewriter (Log, targetArch, classifier, resolver, lookupInfo);
+ return rewriter.Rewrite (brokenExceptionTransitionsEnabled, RewrittenAssembliesOutputDirectory);
+ }
+
+ ///
+ /// Build output items for assemblies (and PDBs) that were rewritten to the output directory.
+ /// Each output item has the rewritten path as its ItemSpec, all metadata copied from the
+ /// corresponding input assembly, and OriginalItemSpec set to the original path.
+ ///
+ void BuildRewrittenAssembliesOutput (HashSet rewrittenOriginalPaths)
+ {
+ if (rewrittenOriginalPaths.Count == 0) {
+ return;
+ }
+
+ var rewrittenItems = new List ();
+
+ foreach (var item in Assemblies) {
+ if (!rewrittenOriginalPaths.Contains (item.ItemSpec)) {
+ continue;
}
- // Count and report methods that need blittable workaround wrappers
- var wrappedCount = state.Classifier.MarshalMethods.Sum (m => m.Value.Count (m2 => m2.NeedsBlittableWorkaround));
+ string rewrittenPath = Path.Combine (RewrittenAssembliesOutputDirectory, Path.GetFileName (item.ItemSpec));
- if (wrappedCount > 0) {
- // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
- Log.LogDebugMessage ($"[{state.TargetArch}] Number of methods in the project that need marshal method wrappers: {wrappedCount}");
+ // Output item for the rewritten DLL
+ var dllItem = new TaskItem (rewrittenPath);
+ item.CopyMetadataTo (dllItem);
+ dllItem.SetMetadata ("OriginalItemSpec", item.ItemSpec);
+ rewrittenItems.Add (dllItem);
+
+ // Output item for the rewritten PDB, if one was produced
+ string rewrittenPdb = Path.ChangeExtension (rewrittenPath, ".pdb");
+ if (File.Exists (rewrittenPdb)) {
+ string originalPdb = Path.ChangeExtension (item.ItemSpec, ".pdb");
+ var pdbItem = new TaskItem (rewrittenPdb);
+ item.CopyMetadataTo (pdbItem);
+ pdbItem.SetMetadata ("OriginalItemSpec", originalPdb);
+ rewrittenItems.Add (pdbItem);
}
}
- return !Log.HasLoggedErrors;
+ RewrittenAssemblies = rewrittenItems.ToArray ();
+ }
+
+ void ReportStatistics (AndroidTargetArch targetArch, MarshalMethodsCollection classifier)
+ {
+ Log.LogDebugMessage ($"[{targetArch}] Number of generated marshal methods: {classifier.MarshalMethods.Count}");
+
+ if (classifier.DynamicallyRegisteredMarshalMethods.Count > 0) {
+ Log.LogWarning ($"[{targetArch}] Number of methods in the project that will be registered dynamically: {classifier.DynamicallyRegisteredMarshalMethods.Count}");
+ }
+
+ var wrappedCount = classifier.MarshalMethods.Sum (m => m.Value.Count (m2 => m2.NeedsBlittableWorkaround));
+ if (wrappedCount > 0) {
+ // TODO: change to LogWarning once the generator can output code which requires no non-blittable wrappers
+ Log.LogDebugMessage ($"[{targetArch}] Number of methods in the project that need marshal method wrappers: {wrappedCount}");
+ }
+ }
+
+ void GenerateLlvmIr (AndroidTargetArch targetArch, string abi, AndroidRuntime androidRuntime, NativeCodeGenStateObject codeGenState)
+ {
+ var targetAbi = abi.ToLowerInvariant ();
+ var llFilePath = Path.Combine (MarshalMethodsOutputDirectory, $"marshal_methods.{targetAbi}.ll");
+ var (assemblyCount, uniqueAssemblyNames) = GetAssemblyCountAndUniqueNames ();
+
+ MarshalMethodsNativeAssemblyGenerator generator = androidRuntime switch {
+ Tasks.AndroidRuntime.MonoVM => new MarshalMethodsNativeAssemblyGeneratorMonoVM (
+ Log,
+ assemblyCount,
+ uniqueAssemblyNames,
+ codeGenState,
+ EnableManagedMarshalMethodsLookup
+ ),
+ Tasks.AndroidRuntime.CoreCLR => new MarshalMethodsNativeAssemblyGeneratorCoreCLR (
+ Log,
+ uniqueAssemblyNames,
+ codeGenState,
+ EnableManagedMarshalMethodsLookup
+ ),
+ _ => throw new NotSupportedException ($"Internal error: unsupported runtime type '{androidRuntime}'")
+ };
+
+ Directory.CreateDirectory (MarshalMethodsOutputDirectory);
+
+ var module = generator.Construct ();
+ using var writer = MemoryStreamPool.Shared.CreateStreamWriter ();
+ bool fileFullyWritten = false;
+
+ try {
+ generator.Generate (module, targetArch, writer, llFilePath);
+ writer.Flush ();
+ Files.CopyIfStreamChanged (writer.BaseStream, llFilePath);
+ fileFullyWritten = true;
+ Log.LogDebugMessage ($"[{targetArch}] Generated marshal methods LLVM IR: {llFilePath}");
+ } finally {
+ if (!fileFullyWritten) {
+ MonoAndroidHelper.LogTextStreamContents (Log, $"Partial contents of file '{llFilePath}'", writer.BaseStream);
+ }
+ }
}
///
- /// Performs the actual assembly rewriting for a specific target architecture.
- /// Creates and executes the that handles
- /// the low-level assembly modification operations.
+ /// Stamp %(Abi) metadata on every assembly item that doesn't already have it.
+ /// The inner build's @(ResolvedFileToPublish) items carry %(RuntimeIdentifier)
+ /// but not %(Abi) — that is normally set later by ProcessAssemblies in the
+ /// outer build. Downstream code (XAJavaTypeScanner) reads %(Abi) from each
+ /// ITaskItem, so we set it here from the task's RuntimeIdentifier parameter.
///
- /// The native code generation state containing marshal method classifications and resolver.
- ///
- /// Whether to generate code compatible with broken exception transitions.
- /// This affects how wrapper methods handle exceptions during JNI calls.
- ///
- ///
- /// This method delegates the complex assembly rewriting logic to the specialized
- /// class, which handles:
- /// - Adding [UnmanagedCallersOnly] attributes to native callbacks
- /// - Generating wrapper methods for non-blittable types
- /// - Modifying assembly references and imports
- /// - Building managed lookup table entries
- ///
- void RewriteMethods (NativeCodeGenState state, bool brokenExceptionTransitionsEnabled)
+ void EnsureAbiMetadata (string abi)
{
- if (state.Classifier == null) {
- return;
+ foreach (var item in Assemblies) {
+ string? existingAbi = item.GetMetadata ("Abi");
+ if (existingAbi.IsNullOrEmpty ()) {
+ item.SetMetadata ("Abi", abi);
+ }
+ }
+ }
+
+ (int assemblyCount, HashSet uniqueAssemblyNames) GetAssemblyCountAndUniqueNames ()
+ {
+ var assemblyCount = 0;
+ var uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase);
+
+ foreach (var assembly in Assemblies) {
+ var culture = MonoAndroidHelper.GetAssemblyCulture (assembly);
+ var fileName = Path.GetFileName (assembly.ItemSpec);
+ string assemblyName;
+
+ if (culture.IsNullOrEmpty ()) {
+ assemblyName = fileName;
+ } else {
+ assemblyName = $"{culture}/{fileName}";
+ }
+
+ if (uniqueAssemblyNames.Add (assemblyName)) {
+ assemblyCount++;
+ }
}
- var rewriter = new MarshalMethodsAssemblyRewriter (Log, state.TargetArch, state.Classifier, state.Resolver, state.ManagedMarshalMethodsLookupInfo);
- rewriter.Rewrite (brokenExceptionTransitionsEnabled);
+ return (assemblyCount, uniqueAssemblyNames);
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
index 7c6119f5347..1d50aeb7720 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodCecilAdapter.cs
@@ -71,6 +71,32 @@ static NativeCodeGenStateObject CreateNativeCodeGenState (AndroidTargetArch arch
return obj;
}
+ ///
+ /// Creates a from a marshal methods classifier,
+ /// for use in the inner build where the full (with
+ /// JavaTypesForJCW, TypeCache, etc.) is not available. Only populates marshal methods
+ /// data needed for LLVM IR generation.
+ ///
+ public static NativeCodeGenStateObject CreateNativeCodeGenStateObjectFromClassifier (AndroidTargetArch arch, MarshalMethodsCollection classifier)
+ {
+ var obj = new NativeCodeGenStateObject {
+ TargetArch = arch,
+ };
+
+ foreach (var group in classifier.MarshalMethods) {
+ var methods = new List (group.Value.Count);
+
+ foreach (var method in group.Value) {
+ var entry = CreateEntry (method, info: null);
+ methods.Add (entry);
+ }
+
+ obj.MarshalMethods.Add (group.Key, methods);
+ }
+
+ return obj;
+ }
+
static MarshalMethodEntryObject CreateEntry (MarshalMethodEntry entry, ManagedMarshalMethodsLookupInfo? info)
{
var obj = new MarshalMethodEntryObject (
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
index 807632241ba..4f028c3ae52 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsAssemblyRewriter.cs
@@ -39,8 +39,8 @@ public MarshalMethodsAssemblyRewriter (TaskLoggingHelper log, AndroidTargetArch
this.managedMarshalMethodsLookupInfo = managedMarshalMethodsLookupInfo;
}
- // TODO: do away with broken exception transitions, there's no point in supporting them
- public void Rewrite (bool brokenExceptionTransitions)
+ // TODO: do away with broken exception transitions, there's no point in supporting them
+ public HashSet Rewrite (bool brokenExceptionTransitions, string outputDirectory)
{
AssemblyDefinition? monoAndroidRuntime = resolver.Resolve ("Mono.Android.Runtime");
if (monoAndroidRuntime == null) {
@@ -141,78 +141,34 @@ public void Rewrite (bool brokenExceptionTransitions)
managedMarshalMethodLookupGenerator.Generate (classifier.MarshalMethods.Values);
}
- foreach (AssemblyDefinition asm in classifier.AssembliesWithMarshalMethods) {
- string? path = asm.MainModule.FileName;
- if (String.IsNullOrEmpty (path)) {
- throw new InvalidOperationException ($"[{targetArch}] Internal error: assembly '{asm}' does not specify path to its file");
- }
-
- string pathPdb = Path.ChangeExtension (path, ".pdb");
- bool havePdb = File.Exists (pathPdb);
-
- var writerParams = new WriterParameters {
- WriteSymbols = havePdb,
- };
-
- string directory = Path.Combine (Path.GetDirectoryName (path), "new");
- Directory.CreateDirectory (directory);
- string output = Path.Combine (directory, Path.GetFileName (path));
- log.LogDebugMessage ($"[{targetArch}] Writing new version of '{path}' assembly: {output}");
+ var rewrittenOriginalPaths = new HashSet (StringComparer.OrdinalIgnoreCase);
+ Directory.CreateDirectory (outputDirectory);
- // TODO: this should be used eventually, but it requires that all the types are reloaded from the assemblies before typemaps are generated
- // since Cecil doesn't update the MVID in the already loaded types
- //asm.MainModule.Mvid = Guid.NewGuid ();
- asm.Write (output, writerParams);
-
- CopyFile (output, path);
- RemoveFile (output);
-
- if (havePdb) {
- string outputPdb = Path.ChangeExtension (output, ".pdb");
- if (File.Exists (outputPdb)) {
- CopyFile (outputPdb, pathPdb);
- }
- RemoveFile (outputPdb);
- }
+ foreach (AssemblyDefinition asm in classifier.AssembliesWithMarshalMethods) {
+ string? path = asm.MainModule.FileName;
+ if (String.IsNullOrEmpty (path)) {
+ throw new InvalidOperationException ($"[{targetArch}] Internal error: assembly '{asm}' does not specify path to its file");
}
- void CopyFile (string source, string target)
- {
- log.LogDebugMessage ($"[{targetArch}] Copying rewritten assembly: {source} -> {target}");
+ string pathPdb = Path.ChangeExtension (path, ".pdb");
+ bool havePdb = File.Exists (pathPdb);
- string targetBackup = $"{target}.bak";
- if (File.Exists (target)) {
- // Try to avoid sharing violations by first renaming the target
- File.Move (target, targetBackup);
- }
+ var writerParams = new WriterParameters {
+ WriteSymbols = havePdb,
+ };
- File.Copy (source, target, true);
+ string output = Path.Combine (outputDirectory, Path.GetFileName (path));
+ log.LogDebugMessage ($"[{targetArch}] Writing rewritten assembly: {path} -> {output}");
- if (File.Exists (targetBackup)) {
- try {
- File.Delete (targetBackup);
- } catch (Exception ex) {
- // On Windows the deletion may fail, depending on lock state of the original `target` file before the move.
- log.LogDebugMessage ($"[{targetArch}] While trying to delete '{targetBackup}', exception was thrown: {ex}");
- log.LogDebugMessage ($"[{targetArch}] Failed to delete backup file '{targetBackup}', ignoring.");
- }
- }
- }
+ // TODO: this should be used eventually, but it requires that all the types are reloaded from the assemblies before typemaps are generated
+ // since Cecil doesn't update the MVID in the already loaded types
+ //asm.MainModule.Mvid = Guid.NewGuid ();
+ asm.Write (output, writerParams);
- void RemoveFile (string? path)
- {
- if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
- return;
- }
+ rewrittenOriginalPaths.Add (path);
+ }
- try {
- log.LogDebugMessage ($"[{targetArch}] Deleting: {path}");
- File.Delete (path);
- } catch (Exception ex) {
- log.LogWarning ($"[{targetArch}] Unable to delete source file '{path}'");
- log.LogDebugMessage ($"[{targetArch}] {ex.ToString ()}");
- }
- }
+ return rewrittenOriginalPaths;
static bool HasUnmanagedCallersOnlyAttribute (MethodDefinition method)
{
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
index 950f89530bc..9b326926ff0 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs
@@ -209,29 +209,11 @@ public MarshalMethodAssemblyIndexValuePlaceholder (MarshalMethodInfo mmi, Assemb
#pragma warning disable CS0414 // Field is assigned but its value is never used - might be used for debugging or future functionality
readonly LlvmIrCallMarker defaultCallMarker;
#pragma warning restore CS0414
- readonly bool generateEmptyCode;
readonly bool managedMarshalMethodsLookupEnabled;
- readonly AndroidTargetArch targetArch;
readonly NativeCodeGenStateObject? codeGenState;
- protected bool GenerateEmptyCode => generateEmptyCode;
protected List Methods => methods;
- ///
- /// Constructor to be used ONLY when marshal methods are DISABLED
- ///
- protected MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, AndroidTargetArch targetArch, ICollection uniqueAssemblyNames)
- : base (log)
- {
- this.targetArch = targetArch;
- this.uniqueAssemblyNames = uniqueAssemblyNames ?? throw new ArgumentNullException (nameof (uniqueAssemblyNames));
- generateEmptyCode = true;
- defaultCallMarker = LlvmIrCallMarker.Tail;
- }
-
- ///
- /// Constructor to be used ONLY when marshal methods are ENABLED
- ///
protected MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState, bool managedMarshalMethodsLookupEnabled)
: base (log)
{
@@ -239,13 +221,12 @@ protected MarshalMethodsNativeAssemblyGenerator (TaskLoggingHelper log, ICollect
this.codeGenState = codeGenState ?? throw new ArgumentNullException (nameof (codeGenState));
this.managedMarshalMethodsLookupEnabled = managedMarshalMethodsLookupEnabled;
- generateEmptyCode = false;
defaultCallMarker = LlvmIrCallMarker.Tail;
}
void Init ()
{
- if (generateEmptyCode || codeGenState.MarshalMethods.Count == 0) {
+ if (codeGenState.MarshalMethods.Count == 0) {
return;
}
@@ -607,7 +588,7 @@ protected virtual void AddClassCache (LlvmIrModule module)
void AddMarshalMethods (LlvmIrModule module, AssemblyCacheState acs, LlvmIrVariable getFunctionPtrVariable, LlvmIrFunction getFunctionPtrFunction)
{
- if (generateEmptyCode || methods == null || methods.Count == 0) {
+ if (methods == null || methods.Count == 0) {
return;
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs
index e57a943afe9..16534e82abf 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorCoreCLR.cs
@@ -1,19 +1,11 @@
#nullable enable
using System.Collections.Generic;
using Microsoft.Build.Utilities;
-using Xamarin.Android.Tools;
namespace Xamarin.Android.Tasks;
class MarshalMethodsNativeAssemblyGeneratorCoreCLR : MarshalMethodsNativeAssemblyGenerator
{
- ///
- /// Constructor to be used ONLY when marshal methods are DISABLED
- ///
- public MarshalMethodsNativeAssemblyGeneratorCoreCLR (TaskLoggingHelper log, AndroidTargetArch targetArch, ICollection uniqueAssemblyNames)
- : base (log, targetArch, uniqueAssemblyNames)
- {}
-
public MarshalMethodsNativeAssemblyGeneratorCoreCLR (TaskLoggingHelper log, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState, bool managedMarshalMethodsLookupEnabled)
: base (log, uniqueAssemblyNames, codeGenState, managedMarshalMethodsLookupEnabled)
{}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs
index 8409dd6bc61..ad10255a376 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGeneratorMonoVM.cs
@@ -42,15 +42,6 @@ sealed class MarshalMethodName
readonly int numberOfAssembliesInApk;
StructureInfo? marshalMethodNameStructureInfo;
- ///
- /// Constructor to be used ONLY when marshal methods are DISABLED
- ///
- public MarshalMethodsNativeAssemblyGeneratorMonoVM (TaskLoggingHelper log, AndroidTargetArch targetArch, int numberOfAssembliesInApk, ICollection uniqueAssemblyNames)
- : base (log, targetArch, uniqueAssemblyNames)
- {
- this.numberOfAssembliesInApk = numberOfAssembliesInApk;
- }
-
public MarshalMethodsNativeAssemblyGeneratorMonoVM (TaskLoggingHelper log, int numberOfAssembliesInApk, ICollection uniqueAssemblyNames, NativeCodeGenStateObject codeGenState, bool managedMarshalMethodsLookupEnabled)
: base (log, uniqueAssemblyNames, codeGenState, managedMarshalMethodsLookupEnabled)
{
@@ -61,7 +52,7 @@ protected override void AddMarshalMethodNames (LlvmIrModule module, AssemblyCach
{
var uniqueMethods = new Dictionary ();
- if (!GenerateEmptyCode && Methods != null) {
+ if (Methods != null) {
foreach (MarshalMethodInfo mmi in Methods) {
string asmName = Path.GetFileName (mmi.Method.NativeCallback.DeclaringType.Module.Assembly.MainModuleFileName);
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index c2527d1954e..30cff04e4b0 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -1758,15 +1758,10 @@ because xbuild doesn't support framework reference assemblies.