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. -