22using System . IO ;
33using System . Linq ;
44using System . Reflection ;
5+ using System . Reflection . Metadata ;
6+ using System . Reflection . PortableExecutable ;
57using System . Text ;
68using System . Collections . Generic ;
79
1517
1618namespace Xamarin . MacDev . Tasks {
1719 public class UnpackLibraryResources : XamarinTask , ITaskCallback , ICancelableTask {
18- MetadataLoadContext ? universe ;
1920 List < ITaskItem > unpackedResources = new List < ITaskItem > ( ) ;
2021
2122 #region Inputs
@@ -26,9 +27,6 @@ public class UnpackLibraryResources : XamarinTask, ITaskCallback, ICancelableTas
2627 [ Required ]
2728 public string IntermediateOutputPath { get ; set ; } = string . Empty ;
2829
29- [ Required ]
30- public ITaskItem [ ] ReferenceAssemblies { get ; set ; } = Array . Empty < ITaskItem > ( ) ;
31-
3230 [ Required ]
3331 public ITaskItem [ ] ReferencedLibraries { get ; set ; } = Array . Empty < ITaskItem > ( ) ;
3432
@@ -52,15 +50,6 @@ public class UnpackLibraryResources : XamarinTask, ITaskCallback, ICancelableTas
5250 #endregion
5351
5452 public override bool Execute ( )
55- {
56- try {
57- return ExecuteImpl ( ) ;
58- } finally {
59- universe ? . Dispose ( ) ;
60- }
61- }
62-
63- bool ExecuteImpl ( )
6453 {
6554 if ( ShouldExecuteRemotely ( ) ) {
6655 var result = new TaskRunner ( SessionId , BuildEngine4 ) . RunAsync ( this ) . Result ;
@@ -87,7 +76,7 @@ bool ExecuteImpl ()
8776 Log . LogMessage ( MessageImportance . Low , MSBStrings . M0168 , asm . ItemSpec ) ;
8877 } else {
8978 var perAssemblyOutputPath = Path . Combine ( IntermediateOutputPath , "unpack" , asm . GetMetadata ( "Filename" ) ) ;
90- var extracted = ExtractContentAssembly ( asm . ItemSpec , perAssemblyOutputPath ) . ToArray ( ) ;
79+ var extracted = ExtractContentAssembly ( asm . ItemSpec , perAssemblyOutputPath ) ;
9180
9281 results . AddRange ( extracted ) ;
9382
@@ -113,58 +102,88 @@ bool IsFrameworkAssembly (ITaskItem asm)
113102 return false ;
114103 }
115104
116- IEnumerable < ITaskItem > ExtractContentAssembly ( string assembly , string intermediatePath )
105+ List < ITaskItem > ExtractContentAssembly ( string assembly , string intermediatePath )
117106 {
107+ var rv = new List < ITaskItem > ( ) ;
108+
118109 if ( ! File . Exists ( assembly ) ) {
119110 Log . LogMessage ( MessageImportance . Low , $ "Not inspecting assembly because it doesn't exist: { assembly } ") ;
120- yield break ;
111+ return rv ;
121112 }
122113
123- var asmWriteTime = File . GetLastWriteTimeUtc ( assembly ) ;
124- var manifestResources = GetAssemblyManifestResources ( assembly ) . ToArray ( ) ;
125- if ( ! manifestResources . Any ( ) )
126- yield break ;
127-
128- Log . LogMessage ( MessageImportance . Low , $ "Inspecting assembly with { manifestResources . Length } resources: { assembly } ") ;
129- foreach ( var embedded in manifestResources ) {
130- string rpath ;
131-
132- if ( embedded . Name . StartsWith ( "__" + Prefix + "_content_" , StringComparison . Ordinal ) ) {
133- var mangled = embedded . Name . Substring ( ( "__" + Prefix + "_content_" ) . Length ) ;
134- rpath = UnmangleResource ( mangled ) ;
135- } else if ( embedded . Name . StartsWith ( "__" + Prefix + "_page_" , StringComparison . Ordinal ) ) {
136- var mangled = embedded . Name . Substring ( ( "__" + Prefix + "_page_" ) . Length ) ;
137- rpath = UnmangleResource ( mangled ) ;
138- } else {
139- continue ;
140- }
141-
142- var path = Path . Combine ( intermediatePath , rpath ) ;
143- var file = new FileInfo ( path ) ;
144-
145- var item = new TaskItem ( path ) ;
146- item . SetMetadata ( "LogicalName" , rpath ) ;
147- item . SetMetadata ( "Optimize" , "false" ) ;
148-
149- if ( file . Exists && file . LastWriteTimeUtc >= asmWriteTime ) {
150- Log . LogMessage ( " Up to date: {0}" , rpath ) ;
151- } else {
152- Log . LogMessage ( " Unpacking: {0}" , rpath ) ;
153-
154- Directory . CreateDirectory ( Path . GetDirectoryName ( path ) ) ;
114+ try {
115+ var asmWriteTime = File . GetLastWriteTimeUtc ( assembly ) ;
116+ using var peStream = File . OpenRead ( assembly ) ;
117+ using var peReader = new PEReader ( peStream ) ;
118+ var metadataReader = PEReaderExtensions . GetMetadataReader ( peReader ) ;
119+ Log . LogMessage ( MessageImportance . Low , $ "Inspecting resources in assembly { assembly } ") ;
120+ foreach ( var manifestResourceHandle in metadataReader . ManifestResources ) {
121+ var manifestResource = metadataReader . GetManifestResource ( manifestResourceHandle ) ;
122+ if ( ! manifestResource . Implementation . IsNil )
123+ continue ; // embedded resources have Implementation.IsNil = true, and those are the ones we care about
124+
125+ var name = metadataReader . GetString ( manifestResource . Name ) ;
126+ if ( string . IsNullOrEmpty ( name ) )
127+ continue ;
128+
129+ string rpath ;
130+
131+ if ( name . StartsWith ( "__" + Prefix + "_content_" , StringComparison . Ordinal ) ) {
132+ var mangled = name . Substring ( ( "__" + Prefix + "_content_" ) . Length ) ;
133+ rpath = UnmangleResource ( mangled ) ;
134+ } else if ( name . StartsWith ( "__" + Prefix + "_page_" , StringComparison . Ordinal ) ) {
135+ var mangled = name . Substring ( ( "__" + Prefix + "_page_" ) . Length ) ;
136+ rpath = UnmangleResource ( mangled ) ;
137+ } else {
138+ continue ;
139+ }
155140
156- using ( var stream = File . Open ( path , FileMode . Create ) ) {
157- using ( var resource = embedded . Open ( ) )
158- resource . CopyTo ( stream ) ;
141+ var path = Path . Combine ( intermediatePath , rpath ) ;
142+ var file = new FileInfo ( path ) ;
143+
144+ var item = new TaskItem ( path ) ;
145+ item . SetMetadata ( "LogicalName" , rpath ) ;
146+ item . SetMetadata ( "Optimize" , "false" ) ;
147+
148+ if ( file . Exists && file . LastWriteTimeUtc >= asmWriteTime ) {
149+ Log . LogMessage ( " Up to date: {0}" , rpath ) ;
150+ } else {
151+ Log . LogMessage ( " Unpacking: {0}" , rpath ) ;
152+
153+ Directory . CreateDirectory ( Path . GetDirectoryName ( path ) ) ;
154+
155+ var resourceDirectory = peReader . GetSectionData ( peReader . PEHeaders . CorHeader ! . ResourcesDirectory . RelativeVirtualAddress ) ;
156+ var reader = resourceDirectory . GetReader ( ( int ) manifestResource . Offset , resourceDirectory . Length - ( int ) manifestResource . Offset ) ;
157+ var length = reader . ReadUInt32 ( ) ;
158+ if ( length > reader . RemainingBytes )
159+ throw new BadImageFormatException ( ) ;
160+ #if NET
161+ using var fs = new FileStream ( path , FileMode . Create , FileAccess . Write , FileShare . Read ) ;
162+ unsafe {
163+ var span = new ReadOnlySpan < byte > ( reader . CurrentPointer , ( int ) length ) ;
164+ fs . Write ( span ) ;
165+ }
166+ #else
167+ var buffer = new byte [ 4096 ] ;
168+ using var fs = new FileStream ( path , FileMode . Create , FileAccess . Write , FileShare . Read , buffer . Length ) ;
169+ var left = ( int ) length ;
170+ while ( left > 0 ) {
171+ var read = Math . Min ( left , buffer . Length ) ;
172+ reader . ReadBytes ( read , buffer , 0 ) ;
173+ fs . Write ( buffer , 0 , read ) ;
174+ left -= read ;
175+ }
176+ #endif
177+ unpackedResources . Add ( item ) ;
159178 }
160179
161- unpackedResources . Add ( item ) ;
180+ rv . Add ( item ) ;
162181 }
163-
164- yield return item ;
182+ } catch ( Exception e ) {
183+ Log . LogMessage ( MessageImportance . Low , $ "Unable to load the resources from the assembly '{ assembly } ': { e } ") ;
184+ return new List < ITaskItem > ( ) ;
165185 }
166-
167- yield break ;
186+ return rv ;
168187 }
169188
170189 static string UnmangleResource ( string mangled )
@@ -237,27 +256,5 @@ public bool ShouldCopyToBuildServer (ITaskItem item)
237256
238257 public IEnumerable < ITaskItem > GetAdditionalItemsToBeCopied ( ) => ItemsFiles ;
239258
240- IEnumerable < ManifestResource > GetAssemblyManifestResources ( string fileName )
241- {
242- if ( universe is null )
243- universe = new MetadataLoadContext ( new PathAssemblyResolver ( ReferenceAssemblies . Select ( v => v . ItemSpec ) ) ) ;
244-
245- Assembly assembly ;
246- try {
247- assembly = universe . LoadFromAssemblyPath ( fileName ) ;
248- } catch ( Exception e ) {
249- Log . LogMessage ( MessageImportance . Low , $ "Unable to load the assembly '{ fileName } : { e } ") ;
250- yield break ;
251- }
252-
253- foreach ( var resourceName in assembly . GetManifestResourceNames ( ) ) {
254- if ( string . IsNullOrEmpty ( resourceName ) )
255- continue ;
256- var info = assembly . GetManifestResourceInfo ( resourceName ) ;
257- if ( ! info . ResourceLocation . HasFlag ( ResourceLocation . Embedded ) )
258- continue ;
259- yield return new ManifestResource ( resourceName , ( ) => assembly . GetManifestResourceStream ( resourceName ) ) ;
260- }
261- }
262259 }
263260}
0 commit comments