From 00444d1507d3707f606c02f6644b40efc10f1583 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 1 Oct 2024 16:20:23 +0200 Subject: [PATCH] [msbuild] Don't try to look for library resources to unpack from BCL assemblies. Fixes #19511. (#21305) We know BCL won't have library resources to unpack, so there's no need to spend any time looking at them. This also required porting the UnpackLibraryResources task to use System.Reflection.Metadata, because MetadataLoadContext requires the reference assemblies to be available to resolve assembly dependencies (and the idea is to not have to pass any reference assemblies to the task). Fixes https://github.com/xamarin/xamarin-macios/issues/19511. Fixes https://github.com/xamarin/xamarin-macios/issues/15030. --- .../Tasks/UnpackLibraryResources.cs | 149 +++++++++--------- msbuild/Xamarin.Shared/Xamarin.Shared.targets | 6 +- 2 files changed, 76 insertions(+), 79 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs index 52c9b5357c..8c6d713e2e 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/UnpackLibraryResources.cs @@ -2,6 +2,8 @@ using System; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Text; using System.Collections.Generic; @@ -15,7 +17,6 @@ using Xamarin.Messaging.Build.Client; namespace Xamarin.MacDev.Tasks { public class UnpackLibraryResources : XamarinTask, ITaskCallback, ICancelableTask { - MetadataLoadContext? universe; List unpackedResources = new List (); #region Inputs @@ -26,9 +27,6 @@ namespace Xamarin.MacDev.Tasks { [Required] public string IntermediateOutputPath { get; set; } = string.Empty; - [Required] - public ITaskItem [] ReferenceAssemblies { get; set; } = Array.Empty (); - [Required] public ITaskItem [] ReferencedLibraries { get; set; } = Array.Empty (); @@ -52,15 +50,6 @@ namespace Xamarin.MacDev.Tasks { #endregion public override bool Execute () - { - try { - return ExecuteImpl (); - } finally { - universe?.Dispose (); - } - } - - bool ExecuteImpl () { if (ShouldExecuteRemotely ()) { var result = new TaskRunner (SessionId, BuildEngine4).RunAsync (this).Result; @@ -87,7 +76,7 @@ namespace Xamarin.MacDev.Tasks { Log.LogMessage (MessageImportance.Low, MSBStrings.M0168, asm.ItemSpec); } else { var perAssemblyOutputPath = Path.Combine (IntermediateOutputPath, "unpack", asm.GetMetadata ("Filename")); - var extracted = ExtractContentAssembly (asm.ItemSpec, perAssemblyOutputPath).ToArray (); + var extracted = ExtractContentAssembly (asm.ItemSpec, perAssemblyOutputPath); results.AddRange (extracted); @@ -113,58 +102,88 @@ namespace Xamarin.MacDev.Tasks { return false; } - IEnumerable ExtractContentAssembly (string assembly, string intermediatePath) + List ExtractContentAssembly (string assembly, string intermediatePath) { + var rv = new List (); + if (!File.Exists (assembly)) { Log.LogMessage (MessageImportance.Low, $"Not inspecting assembly because it doesn't exist: {assembly}"); - yield break; + return rv; } - var asmWriteTime = File.GetLastWriteTimeUtc (assembly); - var manifestResources = GetAssemblyManifestResources (assembly).ToArray (); - if (!manifestResources.Any ()) - yield break; + try { + var asmWriteTime = File.GetLastWriteTimeUtc (assembly); + using var peStream = File.OpenRead (assembly); + using var peReader = new PEReader (peStream); + var metadataReader = PEReaderExtensions.GetMetadataReader (peReader); + Log.LogMessage (MessageImportance.Low, $"Inspecting resources in assembly {assembly}"); + foreach (var manifestResourceHandle in metadataReader.ManifestResources) { + var manifestResource = metadataReader.GetManifestResource (manifestResourceHandle); + if (!manifestResource.Implementation.IsNil) + continue; // embedded resources have Implementation.IsNil = true, and those are the ones we care about - Log.LogMessage (MessageImportance.Low, $"Inspecting assembly with {manifestResources.Length} resources: {assembly}"); - foreach (var embedded in manifestResources) { - string rpath; + var name = metadataReader.GetString (manifestResource.Name); + if (string.IsNullOrEmpty (name)) + continue; - if (embedded.Name.StartsWith ("__" + Prefix + "_content_", StringComparison.Ordinal)) { - var mangled = embedded.Name.Substring (("__" + Prefix + "_content_").Length); - rpath = UnmangleResource (mangled); - } else if (embedded.Name.StartsWith ("__" + Prefix + "_page_", StringComparison.Ordinal)) { - var mangled = embedded.Name.Substring (("__" + Prefix + "_page_").Length); - rpath = UnmangleResource (mangled); - } else { - continue; - } + string rpath; - var path = Path.Combine (intermediatePath, rpath); - var file = new FileInfo (path); - - var item = new TaskItem (path); - item.SetMetadata ("LogicalName", rpath); - item.SetMetadata ("Optimize", "false"); - - if (file.Exists && file.LastWriteTimeUtc >= asmWriteTime) { - Log.LogMessage (" Up to date: {0}", rpath); - } else { - Log.LogMessage (" Unpacking: {0}", rpath); - - Directory.CreateDirectory (Path.GetDirectoryName (path)); - - using (var stream = File.Open (path, FileMode.Create)) { - using (var resource = embedded.Open ()) - resource.CopyTo (stream); + if (name.StartsWith ("__" + Prefix + "_content_", StringComparison.Ordinal)) { + var mangled = name.Substring (("__" + Prefix + "_content_").Length); + rpath = UnmangleResource (mangled); + } else if (name.StartsWith ("__" + Prefix + "_page_", StringComparison.Ordinal)) { + var mangled = name.Substring (("__" + Prefix + "_page_").Length); + rpath = UnmangleResource (mangled); + } else { + continue; } - unpackedResources.Add (item); + var path = Path.Combine (intermediatePath, rpath); + var file = new FileInfo (path); + + var item = new TaskItem (path); + item.SetMetadata ("LogicalName", rpath); + item.SetMetadata ("Optimize", "false"); + + if (file.Exists && file.LastWriteTimeUtc >= asmWriteTime) { + Log.LogMessage (" Up to date: {0}", rpath); + } else { + Log.LogMessage (" Unpacking: {0}", rpath); + + Directory.CreateDirectory (Path.GetDirectoryName (path)); + + var resourceDirectory = peReader.GetSectionData (peReader.PEHeaders.CorHeader!.ResourcesDirectory.RelativeVirtualAddress); + var reader = resourceDirectory.GetReader ((int) manifestResource.Offset, resourceDirectory.Length - (int) manifestResource.Offset); + var length = reader.ReadUInt32 (); + if (length > reader.RemainingBytes) + throw new BadImageFormatException (); +#if NET + using var fs = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.Read); + unsafe { + var span = new ReadOnlySpan (reader.CurrentPointer, (int) length); + fs.Write (span); + } +#else + var buffer = new byte [4096]; + using var fs = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.Read, buffer.Length); + var left = (int) length; + while (left > 0) { + var read = Math.Min (left, buffer.Length); + reader.ReadBytes (read, buffer, 0); + fs.Write (buffer, 0, read); + left -= read; + } +#endif + unpackedResources.Add (item); + } + + rv.Add (item); } - - yield return item; + } catch (Exception e) { + Log.LogMessage (MessageImportance.Low, $"Unable to load the resources from the assembly '{assembly}': {e}"); + return new List (); } - - yield break; + return rv; } static string UnmangleResource (string mangled) @@ -237,27 +256,5 @@ namespace Xamarin.MacDev.Tasks { public IEnumerable GetAdditionalItemsToBeCopied () => ItemsFiles; - IEnumerable GetAssemblyManifestResources (string fileName) - { - if (universe is null) - universe = new MetadataLoadContext (new PathAssemblyResolver (ReferenceAssemblies.Select (v => v.ItemSpec))); - - Assembly assembly; - try { - assembly = universe.LoadFromAssemblyPath (fileName); - } catch (Exception e) { - Log.LogMessage (MessageImportance.Low, $"Unable to load the assembly '{fileName}: {e}"); - yield break; - } - - foreach (var resourceName in assembly.GetManifestResourceNames ()) { - if (string.IsNullOrEmpty (resourceName)) - continue; - var info = assembly.GetManifestResourceInfo (resourceName); - if (!info.ResourceLocation.HasFlag (ResourceLocation.Embedded)) - continue; - yield return new ManifestResource (resourceName, () => assembly.GetManifestResourceStream (resourceName)); - } - } } } diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 22556e3f38..b9e1388c96 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -1933,7 +1933,9 @@ Copyright (C) 2018 Microsoft. All rights reserved. <_StampDirectory>$(DeviceSpecificIntermediateOutputPath)resourcestamps\ - <_UnpackLibraryResourceItems Include="@(ReferencePath);@(ReferenceDependencyPaths)"> + <_UnpackLibraryResourceItems Include="@(ReferencePath)" Condition="'%(ReferencePath.FrameworkReferenceName)' != 'Microsoft.NETCore.App' And '%(ReferencePath.FrameworkReferenceName)' != 'Microsoft.$(_PlatformName)'" /> + <_UnpackLibraryResourceItems Include="@(ReferenceDependencyPaths)" Condition="'%(ReferenceDependencyPaths.FrameworkReferenceName)' != 'Microsoft.NETCore.App' And '%(ReferenceDependencyPaths.FrameworkReferenceName)' != 'Microsoft.$(_PlatformName)'" /> + <_UnpackLibraryResourceItems> $(_StampDirectory)%(FileName).stamp $(_StampDirectory)%(FileName).items @@ -1982,14 +1984,12 @@ Copyright (C) 2018 Microsoft. All rights reserved. -