From c5ecca95ffa0a1d416098c4b2472f95f829d21d8 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 5 Mar 2026 15:50:16 -0800 Subject: [PATCH 1/7] [xabt] Move StripEmbeddedLibraries from ILLink custom step to standalone MSBuild task and pipeline step Move embedded resource stripping out of the ILLink custom step so it runs after ILLink but before ReadyToRun/crossgen2 in the inner build, ensuring R2R images are generated from already-stripped assemblies. - Trimmed builds: new StripEmbeddedLibraries MSBuild task runs via _StripEmbeddedLibraries target (AfterTargets="ILLink") in the inner build, processing assemblies in-place with temp-write-copy-back. - Non-trimmed builds: new StripEmbeddedLibrariesStep added to LinkAssembliesNoShrink pipeline. - Shared ShouldStripResource() logic between both paths. - Removed old _TrimmerCustomSteps registration and deleted the ILLink StripEmbeddedLibraries.cs (now dead code). --- .../StripEmbeddedLibraries.cs | 56 ------ .../StripEmbeddedLibrariesStep.cs | 38 ++++ ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 19 +- .../Tasks/LinkAssembliesNoShrink.cs | 3 + .../Tasks/StripEmbeddedLibraries.cs | 165 ++++++++++++++++++ .../Xamarin.Android.Build.Tasks.csproj | 1 + 6 files changed, 225 insertions(+), 57 deletions(-) delete mode 100644 src/Microsoft.Android.Sdk.ILLink/StripEmbeddedLibraries.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/StripEmbeddedLibraries.cs b/src/Microsoft.Android.Sdk.ILLink/StripEmbeddedLibraries.cs deleted file mode 100644 index 041bead3f92..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/StripEmbeddedLibraries.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Mono.Cecil; -using Mono.Linker; -using Mono.Linker.Steps; -using System; -using System.Linq; -using Xamarin.Android.Tasks; - -namespace MonoDroid.Tuner -{ - public class StripEmbeddedLibraries : BaseStep - { - protected override void ProcessAssembly (AssemblyDefinition assembly) - { - if (!Annotations.HasAction (assembly)) - return; - var action = Annotations.GetAction (assembly); - if (action == AssemblyAction.Skip || action == AssemblyAction.Delete) - return; - - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) - return; - bool assembly_modified = false; - foreach (var mod in assembly.Modules) { - foreach (var r in mod.Resources.ToArray ()) { - if (ShouldStripResource (r)) { - Context.LogMessage ($" Stripped {r.Name} from {assembly.Name.Name}.dll"); - mod.Resources.Remove (r); - assembly_modified = true; - } - } - } - if (assembly_modified && action == AssemblyAction.Copy) { - Annotations.SetAction (assembly, AssemblyAction.Save); - } - } - - 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/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs new file mode 100644 index 00000000000..b6f12bf5345 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using Microsoft.Build.Utilities; +using Mono.Cecil; + +namespace Xamarin.Android.Tasks; + +/// +/// An IAssemblyModifierPipelineStep that strips embedded Android resources (.jar, +/// __AndroidNativeLibraries__.zip, __AndroidLibraryProjects__.zip, __AndroidEnvironment__) +/// from assemblies in non-trimmed builds. This step is added to LinkAssembliesNoShrink's pipeline. +/// +public class StripEmbeddedLibrariesStep : IAssemblyModifierPipelineStep +{ + public TaskLoggingHelper Log { get; set; } + + public StripEmbeddedLibrariesStep (TaskLoggingHelper log) + { + Log = log; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + // Skip framework assemblies -- they do not have embedded Android resources + if (context.IsFrameworkAssembly) + return; + + foreach (var module in assembly.Modules) { + foreach (var resource in module.Resources.ToArray ()) { + if (StripEmbeddedLibraries.ShouldStripResource (resource)) { + Log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); + module.Resources.Remove (resource); + context.IsAssemblyModified = true; + } + } + } + } +} 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..84ead14154f 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 @@ -7,6 +7,7 @@ + <_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag @@ -203,7 +204,6 @@ Type="MonoDroid.Tuner.AddKeepAlivesStep" /> - <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" AfterStep="CleanStep" Type="MonoDroid.Tuner.StripEmbeddedLibraries" /> <_TrimmerCustomSteps Condition=" '$(AndroidLinkResources)' == 'true' " Include="$(_AndroidLinkerCustomStepAssembly)" @@ -250,6 +250,23 @@ + + + + <_StripEmbeddedLibrariesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs index 9dc7e99e635..7024a91d0be 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs @@ -39,6 +39,9 @@ protected override void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCon pipeline.Steps.Add (addKeepAliveStep); } + // StripEmbeddedLibrariesStep - strip embedded .jar, native libs, etc. for non-trimmed builds + pipeline.Steps.Add (new StripEmbeddedLibrariesStep (Log)); + // Ensure the task's steps are added base.BuildPipeline (pipeline, context); } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs new file mode 100644 index 00000000000..4d2bd2a0a1c --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs @@ -0,0 +1,165 @@ +#nullable enable + +using System; +using System.IO; +using System.Linq; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Mono.Cecil; + +namespace Xamarin.Android.Tasks; + +/// +/// An MSBuild task that strips embedded Android resources (.jar, __AndroidNativeLibraries__.zip, +/// __AndroidLibraryProjects__.zip, __AndroidEnvironment__) from trimmed assemblies. +/// +/// This runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, +/// so that R2R images are generated from the already-stripped assemblies. +/// +public class StripEmbeddedLibraries : AndroidTask +{ + public override string TaskPrefix => "SEL"; + + [Required] + public ITaskItem [] Assemblies { get; set; } = []; + + public bool Deterministic { get; set; } + + public override bool RunTask () + { + foreach (var assembly in Assemblies) { + // Skip framework assemblies -- they do not have embedded Android resources + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { + continue; + } + + StripAssembly (assembly.ItemSpec); + } + + return !Log.HasLoggedErrors; + } + + void StripAssembly (string assemblyPath) + { + string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb"); + bool havePdb = File.Exists (pdbPath); + + var readerParams = new ReaderParameters { + ReadSymbols = havePdb, + ReadWrite = false, + }; + + bool assembly_modified = false; + + using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { + foreach (var module in assembly.Modules) { + foreach (var resource in module.Resources.ToArray ()) { + if (ShouldStripResource (resource)) { + Log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); + module.Resources.Remove (resource); + assembly_modified = true; + } + } + } + + if (!assembly_modified) { + return; + } + + // Write modified assembly using the write-to-temp-then-copy pattern + // from MarshalMethodsAssemblyRewriter to avoid file locking issues. + string directory = Path.Combine (Path.GetDirectoryName (assemblyPath), "stripped"); + Directory.CreateDirectory (directory); + string tempOutput = Path.Combine (directory, Path.GetFileName (assemblyPath)); + + var writerParams = new WriterParameters { + WriteSymbols = havePdb, + DeterministicMvid = Deterministic, + }; + + Log.LogDebugMessage ($" Writing stripped assembly: {assemblyPath}"); + assembly.Write (tempOutput, writerParams); + + CopyFile (tempOutput, assemblyPath); + RemoveFile (tempOutput); + + if (havePdb) { + string tempPdb = Path.ChangeExtension (tempOutput, ".pdb"); + if (File.Exists (tempPdb)) { + CopyFile (tempPdb, pdbPath); + } + RemoveFile (tempPdb); + } + + // Clean up temp directory if empty + try { + if (Directory.Exists (directory) && !Directory.EnumerateFileSystemEntries (directory).Any ()) { + Directory.Delete (directory); + } + } catch (Exception) { + // Ignore cleanup failures + } + } + } + + /// + /// Determines whether a resource should be stripped from the assembly. + /// Matches the same criteria as the old ILLink StripEmbeddedLibraries step. + /// + internal static bool ShouldStripResource (Resource resource) + { + if (!(resource is EmbeddedResource)) + return false; + // Embedded jars + if (resource.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase)) + return true; + // Embedded AndroidNativeLibrary archive + if (resource.Name == "__AndroidNativeLibraries__.zip") + return true; + // Embedded AndroidResourceLibrary archive + if (resource.Name == "__AndroidLibraryProjects__.zip") + return true; + // Embedded AndroidEnvironment items + if (resource.Name.StartsWith ("__AndroidEnvironment__", StringComparison.Ordinal)) + return true; + return false; + } + + void CopyFile (string source, string target) + { + Log.LogDebugMessage ($" Copying stripped 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) { + 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 temporary file '{path}'"); + Log.LogDebugMessage ($" {ex}"); + } + } +} 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 @@ + From d6c80a2b8f5144cb1b2608552b4cecbb7eb60939 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 6 Mar 2026 10:11:41 -0800 Subject: [PATCH 2/7] Fix missing using directive for LogDebugMessage extension method --- .../Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs index b6f12bf5345..646475f1bfe 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; using Mono.Cecil; From fa2606c9c5bbc254fcaec533da160403d3335525 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 6 Mar 2026 15:12:37 -0800 Subject: [PATCH 3/7] Fix Cecil AssemblyResolutionException by adding DefaultAssemblyResolver with search directories When Cecil writes a modified assembly, it resolves type references during metadata building (MetadataBuilder.GetConstantType). Without an assembly resolver configured with search directories, this fails with AssemblyResolutionException for references like Mono.Android. Add a DefaultAssemblyResolver populated with unique directories from the assembly list, matching the pattern used by AssemblyModifierPipeline. --- .../Tasks/StripEmbeddedLibraries.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs index 4d2bd2a0a1c..42660622ef7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Android.Build.Tasks; @@ -28,19 +29,32 @@ public class StripEmbeddedLibraries : AndroidTask public override bool RunTask () { + var resolver = new DefaultAssemblyResolver (); + var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase); + foreach (var assembly in Assemblies) { - // Skip framework assemblies -- they do not have embedded Android resources - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { - continue; + var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec)); + if (searchDirectories.Add (dir)) { + resolver.AddSearchDirectory (dir); } + } + + try { + foreach (var assembly in Assemblies) { + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { + continue; + } - StripAssembly (assembly.ItemSpec); + StripAssembly (assembly.ItemSpec, resolver); + } + } finally { + resolver.Dispose (); } return !Log.HasLoggedErrors; } - void StripAssembly (string assemblyPath) + void StripAssembly (string assemblyPath, IAssemblyResolver resolver) { string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb"); bool havePdb = File.Exists (pdbPath); @@ -48,6 +62,7 @@ void StripAssembly (string assemblyPath) var readerParams = new ReaderParameters { ReadSymbols = havePdb, ReadWrite = false, + AssemblyResolver = resolver, }; bool assembly_modified = false; From f4155d84673734190f9617141591e39709d5fb6c Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Sat, 7 Mar 2026 22:59:46 -0800 Subject: [PATCH 4/7] Fix Windows file locking in StripEmbeddedLibraries by closing Cecil handles before file copy-back --- .../Tasks/StripEmbeddedLibraries.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs index 42660622ef7..28517538c5a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs @@ -33,7 +33,7 @@ public override bool RunTask () var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase); foreach (var assembly in Assemblies) { - var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec)); + var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); if (searchDirectories.Add (dir)) { resolver.AddSearchDirectory (dir); } @@ -66,6 +66,8 @@ void StripAssembly (string assemblyPath, IAssemblyResolver resolver) }; bool assembly_modified = false; + string directory = Path.Combine (Path.GetDirectoryName (assemblyPath) ?? "", "stripped"); + string tempOutput = Path.Combine (directory, Path.GetFileName (assemblyPath)); using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { foreach (var module in assembly.Modules) { @@ -82,11 +84,7 @@ void StripAssembly (string assemblyPath, IAssemblyResolver resolver) return; } - // Write modified assembly using the write-to-temp-then-copy pattern - // from MarshalMethodsAssemblyRewriter to avoid file locking issues. - string directory = Path.Combine (Path.GetDirectoryName (assemblyPath), "stripped"); Directory.CreateDirectory (directory); - string tempOutput = Path.Combine (directory, Path.GetFileName (assemblyPath)); var writerParams = new WriterParameters { WriteSymbols = havePdb, @@ -95,26 +93,25 @@ void StripAssembly (string assemblyPath, IAssemblyResolver resolver) Log.LogDebugMessage ($" Writing stripped assembly: {assemblyPath}"); assembly.Write (tempOutput, writerParams); + } - CopyFile (tempOutput, assemblyPath); - RemoveFile (tempOutput); + CopyFile (tempOutput, assemblyPath); + RemoveFile (tempOutput); - if (havePdb) { - string tempPdb = Path.ChangeExtension (tempOutput, ".pdb"); - if (File.Exists (tempPdb)) { - CopyFile (tempPdb, pdbPath); - } - RemoveFile (tempPdb); + if (havePdb) { + string tempPdb = Path.ChangeExtension (tempOutput, ".pdb"); + if (File.Exists (tempPdb)) { + CopyFile (tempPdb, pdbPath); } + RemoveFile (tempPdb); + } - // Clean up temp directory if empty - try { - if (Directory.Exists (directory) && !Directory.EnumerateFileSystemEntries (directory).Any ()) { - Directory.Delete (directory); - } - } catch (Exception) { - // Ignore cleanup failures + try { + if (Directory.Exists (directory) && !Directory.EnumerateFileSystemEntries (directory).Any ()) { + Directory.Delete (directory); } + } catch (Exception) { + // Ignore cleanup failures } } From 452cca86348fe8c59ac6cc77e3cbbdf3a4316171 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 12 Mar 2026 13:41:35 -0700 Subject: [PATCH 5/7] Address PR review feedback: nullable, error handling, and target condition - Add #nullable enable to StripEmbeddedLibrariesStep.cs - Log exceptions in catch blocks instead of silently swallowing them - Downgrade RemoveFile warning to LogDebugMessage (matches prevailing pattern for temp file cleanup failures) - Add MSBuildLastTaskResult check to _StripEmbeddedLibraries target to skip when ILLink fails --- .../Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs | 2 ++ .../targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets | 2 +- .../Tasks/StripEmbeddedLibraries.cs | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs index 646475f1bfe..610a48abb0b 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Linq; using Microsoft.Android.Build.Tasks; 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 84ead14154f..002888ab61f 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 @@ -258,7 +258,7 @@ --> + Condition=" '$(PublishTrimmed)' == 'true' and '$(MSBuildLastTaskResult)' != 'false' "> <_StripEmbeddedLibrariesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs index 28517538c5a..d3a2fa1b7a7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs @@ -110,8 +110,9 @@ void StripAssembly (string assemblyPath, IAssemblyResolver resolver) if (Directory.Exists (directory) && !Directory.EnumerateFileSystemEntries (directory).Any ()) { Directory.Delete (directory); } - } catch (Exception) { - // Ignore cleanup failures + } catch (Exception ex) { + Log.LogDebugMessage ($" Failed to clean up directory '{directory}'"); + Log.LogDebugMessage ($" {ex}"); } } @@ -170,7 +171,7 @@ void RemoveFile (string? path) Log.LogDebugMessage ($" Deleting: {path}"); File.Delete (path); } catch (Exception ex) { - Log.LogWarning ($"Unable to delete temporary file '{path}'"); + Log.LogDebugMessage ($" Unable to delete temporary file '{path}'"); Log.LogDebugMessage ($" {ex}"); } } From 803c7791a280f1a3cb4c79da987d58c2cb72feb5 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 13 Mar 2026 10:19:16 -0700 Subject: [PATCH 6/7] Remove StripEmbeddedLibrariesStep from non-trimmed builds and drop MSBuildLastTaskResult check Stripping embedded resources was never done for non-trimmed (Debug) builds before, so adding it to LinkAssembliesNoShrink was a behavioral change that would unnecessarily modify and rewrite assemblies. Remove the pipeline step and keep stripping only in the trimmed-build MSBuild task path. Also revert the MSBuildLastTaskResult condition on _StripEmbeddedLibraries: AfterTargets targets do not run when the predecessor fails outright, so the check is unnecessary. --- .../StripEmbeddedLibrariesStep.cs | 41 ------------------- ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 3 +- .../Tasks/LinkAssembliesNoShrink.cs | 3 -- .../Xamarin.Android.Build.Tasks.csproj | 1 - 4 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs deleted file mode 100644 index 610a48abb0b..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs +++ /dev/null @@ -1,41 +0,0 @@ -#nullable enable - -using System; -using System.Linq; -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Utilities; -using Mono.Cecil; - -namespace Xamarin.Android.Tasks; - -/// -/// An IAssemblyModifierPipelineStep that strips embedded Android resources (.jar, -/// __AndroidNativeLibraries__.zip, __AndroidLibraryProjects__.zip, __AndroidEnvironment__) -/// from assemblies in non-trimmed builds. This step is added to LinkAssembliesNoShrink's pipeline. -/// -public class StripEmbeddedLibrariesStep : IAssemblyModifierPipelineStep -{ - public TaskLoggingHelper Log { get; set; } - - public StripEmbeddedLibrariesStep (TaskLoggingHelper log) - { - Log = log; - } - - public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) - { - // Skip framework assemblies -- they do not have embedded Android resources - if (context.IsFrameworkAssembly) - return; - - foreach (var module in assembly.Modules) { - foreach (var resource in module.Resources.ToArray ()) { - if (StripEmbeddedLibraries.ShouldStripResource (resource)) { - Log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); - module.Resources.Remove (resource); - context.IsAssemblyModified = true; - } - } - } - } -} 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 002888ab61f..4be55630c91 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 @@ -254,11 +254,10 @@ Strip embedded Android resources (.jar, native libs, etc.) from trimmed assemblies. Runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, so R2R images are generated from already-stripped assemblies. - For non-trimmed builds, this is done by StripEmbeddedLibrariesStep in LinkAssembliesNoShrink. --> + Condition=" '$(PublishTrimmed)' == 'true' "> <_StripEmbeddedLibrariesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs index 7024a91d0be..9dc7e99e635 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs @@ -39,9 +39,6 @@ protected override void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCon pipeline.Steps.Add (addKeepAliveStep); } - // StripEmbeddedLibrariesStep - strip embedded .jar, native libs, etc. for non-trimmed builds - pipeline.Steps.Add (new StripEmbeddedLibrariesStep (Log)); - // Ensure the task's steps are added base.BuildPipeline (pipeline, context); } 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 b0262d3197b..79f6e450ae3 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -55,7 +55,6 @@ - From 386de838145292195bcc0025984ab7ac150dd92a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 13 Mar 2026 16:12:44 -0500 Subject: [PATCH 7/7] Simplify StripEmbeddedLibraries: use Cecil ReadWrite mode Use ReadWrite = true in ReaderParameters so Cecil can write directly back to the same file, matching the pattern used by RemoveRegisterAttribute. This eliminates the 'stripped' temp directory, the .bak file rename dance in CopyFile, and the RemoveFile cleanup - replacing ~60 lines with a single assembly.Write() call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/StripEmbeddedLibraries.cs | 70 ++----------------- 1 file changed, 4 insertions(+), 66 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs index d3a2fa1b7a7..7a65c4a8145 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs @@ -61,13 +61,11 @@ void StripAssembly (string assemblyPath, IAssemblyResolver resolver) var readerParams = new ReaderParameters { ReadSymbols = havePdb, - ReadWrite = false, + ReadWrite = true, AssemblyResolver = resolver, }; bool assembly_modified = false; - string directory = Path.Combine (Path.GetDirectoryName (assemblyPath) ?? "", "stripped"); - string tempOutput = Path.Combine (directory, Path.GetFileName (assemblyPath)); using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { foreach (var module in assembly.Modules) { @@ -84,35 +82,11 @@ void StripAssembly (string assemblyPath, IAssemblyResolver resolver) return; } - Directory.CreateDirectory (directory); - - var writerParams = new WriterParameters { + Log.LogDebugMessage ($" Writing stripped assembly: {assemblyPath}"); + assembly.Write (new WriterParameters { WriteSymbols = havePdb, DeterministicMvid = Deterministic, - }; - - Log.LogDebugMessage ($" Writing stripped assembly: {assemblyPath}"); - assembly.Write (tempOutput, writerParams); - } - - CopyFile (tempOutput, assemblyPath); - RemoveFile (tempOutput); - - if (havePdb) { - string tempPdb = Path.ChangeExtension (tempOutput, ".pdb"); - if (File.Exists (tempPdb)) { - CopyFile (tempPdb, pdbPath); - } - RemoveFile (tempPdb); - } - - try { - if (Directory.Exists (directory) && !Directory.EnumerateFileSystemEntries (directory).Any ()) { - Directory.Delete (directory); - } - } catch (Exception ex) { - Log.LogDebugMessage ($" Failed to clean up directory '{directory}'"); - Log.LogDebugMessage ($" {ex}"); + }); } } @@ -139,40 +113,4 @@ internal static bool ShouldStripResource (Resource resource) return false; } - void CopyFile (string source, string target) - { - Log.LogDebugMessage ($" Copying stripped 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) { - 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.LogDebugMessage ($" Unable to delete temporary file '{path}'"); - Log.LogDebugMessage ($" {ex}"); - } - } }