diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs
new file mode 100644
index 00000000000..b885433d304
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs
@@ -0,0 +1,58 @@
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+using Mono.Cecil;
+using System;
+using System.Linq;
+using Xamarin.Android.Tasks;
+
+namespace MonoDroid.Tuner
+{
+ public class StripEmbeddedLibrariesStep : IAssemblyModifierPipelineStep
+ {
+ public TaskLoggingHelper Log { get; }
+
+ public StripEmbeddedLibrariesStep (TaskLoggingHelper log)
+ {
+ Log = log;
+ }
+
+ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
+ {
+ if (context.IsFrameworkAssembly)
+ return;
+
+ bool assembly_modified = false;
+ foreach (var mod in assembly.Modules) {
+ foreach (var r in mod.Resources.ToArray ()) {
+ if (ShouldStripResource (r)) {
+ Log.LogDebugMessage ($" Stripped {r.Name} from {assembly.Name.Name}.dll");
+ mod.Resources.Remove (r);
+ assembly_modified = true;
+ }
+ }
+ }
+ if (assembly_modified) {
+ context.IsAssemblyModified = true;
+ }
+ }
+
+ bool ShouldStripResource (Resource r)
+ {
+ if (!(r is EmbeddedResource))
+ return false;
+ // embedded jars
+ if (r.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase))
+ return true;
+ // embedded AndroidNativeLibrary archive
+ if (r.Name == "__AndroidNativeLibraries__.zip")
+ return true;
+ // embedded AndroidResourceLibrary archive
+ if (r.Name == "__AndroidLibraryProjects__.zip")
+ return true;
+ // embedded AndroidEnvironment item
+ if (r.Name.StartsWith ("__AndroidEnvironment__", StringComparison.Ordinal))
+ return true;
+ return false;
+ }
+ }
+}
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 68fd6b77705..5eb1b723ff1 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
@@ -203,7 +203,6 @@
Type="MonoDroid.Tuner.AddKeepAlivesStep"
/>
- <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" AfterStep="CleanStep" Type="MonoDroid.Tuner.StripEmbeddedLibraries" />
<_TrimmerCustomSteps
Condition=" '$(AndroidLinkResources)' == 'true' "
Include="$(_AndroidLinkerCustomStepAssembly)"
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
index 08a558de918..164aa7b9c29 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AssemblyModifierPipeline.cs
@@ -140,6 +140,10 @@ protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCont
findJavaObjectsStep.Initialize (context);
pipeline.Steps.Add (findJavaObjectsStep);
+ // StripEmbeddedLibrariesStep
+ var stripEmbeddedLibrariesStep = new StripEmbeddedLibrariesStep (Log);
+ pipeline.Steps.Add (stripEmbeddedLibrariesStep);
+
// SaveChangedAssemblyStep
var writerParameters = new WriterParameters {
DeterministicMvid = Deterministic,
@@ -192,9 +196,42 @@ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
{
if (context.IsAssemblyModified) {
Log.LogDebugMessage ($"Saving modified assembly: {context.Destination.ItemSpec}");
- Directory.CreateDirectory (Path.GetDirectoryName (context.Destination.ItemSpec));
+
+ // Write back pure IL even for crossgen-ed (R2R) assemblies, matching ILLink's OutputStep behavior.
+ // Mono.Cecil cannot write mixed-mode assemblies, so we strip the R2R metadata before writing.
+ // The native R2R code is discarded since the assembly has been modified and would need to be
+ // re-crossgen'd anyway.
+ foreach (var module in assembly.Modules) {
+ if (IsCrossgened (module)) {
+ module.Attributes |= ModuleAttributes.ILOnly;
+ module.Attributes ^= ModuleAttributes.ILLibrary;
+ module.Architecture = TargetArchitecture.I386; // I386+ILOnly translates to AnyCPU
+ module.Characteristics |= ModuleCharacteristics.NoSEH;
+ }
+ }
+
+ // Write to a temp "new/" subdirectory then immediately copy back, to avoid
+ // reading and writing from the same path. Follows the same pattern as
+ // MarshalMethodsAssemblyRewriter.
+ string destPath = context.Destination.ItemSpec;
+ string directory = Path.Combine (Path.GetDirectoryName (destPath), "new");
+ Directory.CreateDirectory (directory);
+ string tempPath = Path.Combine (directory, Path.GetFileName (destPath));
+
WriterParameters.WriteSymbols = assembly.MainModule.HasSymbols;
- assembly.Write (context.Destination.ItemSpec, WriterParameters);
+ assembly.Write (tempPath, WriterParameters);
+
+ CopyFile (tempPath, destPath);
+ RemoveFile (tempPath);
+
+ if (assembly.MainModule.HasSymbols) {
+ string tempPdb = Path.ChangeExtension (tempPath, ".pdb");
+ string destPdb = Path.ChangeExtension (destPath, ".pdb");
+ if (File.Exists (tempPdb)) {
+ CopyFile (tempPdb, destPdb);
+ }
+ RemoveFile (tempPdb);
+ }
} else {
// If we didn't write a modified file, copy the original to the destination
CopyIfChanged (context.Source, context.Destination);
@@ -204,6 +241,44 @@ public void ProcessAssembly (AssemblyDefinition assembly, StepContext context)
context.IsAssemblyModified = false;
}
+ void CopyFile (string source, string target)
+ {
+ Log.LogDebugMessage ($"Copying rewritten assembly: {source} -> {target}");
+
+ string targetBackup = $"{target}.bak";
+ if (File.Exists (target)) {
+ // Try to avoid sharing violations by first renaming the target
+ File.Move (target, targetBackup);
+ }
+
+ File.Copy (source, target, true);
+
+ 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 ($"While trying to delete '{targetBackup}', exception was thrown: {ex}");
+ Log.LogDebugMessage ($"Failed to delete backup file '{targetBackup}', ignoring.");
+ }
+ }
+ }
+
+ void RemoveFile (string? path)
+ {
+ if (String.IsNullOrEmpty (path) || !File.Exists (path)) {
+ return;
+ }
+
+ try {
+ Log.LogDebugMessage ($"Deleting: {path}");
+ File.Delete (path);
+ } catch (Exception ex) {
+ Log.LogWarning ($"Unable to delete source file '{path}'");
+ Log.LogDebugMessage ($"{ex}");
+ }
+ }
+
void CopyIfChanged (ITaskItem source, ITaskItem destination)
{
if (MonoAndroidHelper.CopyAssemblyAndSymbols (source.ItemSpec, destination.ItemSpec)) {
@@ -215,4 +290,14 @@ void CopyIfChanged (ITaskItem source, ITaskItem destination)
File.SetLastWriteTimeUtc (destination.ItemSpec, DateTime.UtcNow);
}
}
+
+ ///
+ /// Check if a module has been crossgen-ed (ReadyToRun compiled), matching
+ /// ILLink's ModuleDefinitionExtensions.IsCrossgened() implementation.
+ ///
+ static bool IsCrossgened (ModuleDefinition module)
+ {
+ return (module.Attributes & ModuleAttributes.ILOnly) == 0 &&
+ (module.Attributes & ModuleAttributes.ILLibrary) != 0;
+ }
}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
index 79f6e450ae3..b0262d3197b 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -55,6 +55,7 @@
+