@@ -20,16 +20,8 @@ namespace Xamarin.Android.Tasks;
2020/// are run *after* the linker has run. Additionally, this task is run by
2121/// LinkAssembliesNoShrink to modify assemblies when ILLink is not used.
2222/// </summary>
23- public class AssemblyModifierPipeline : AndroidTask
23+ public partial class AssemblyModifierPipeline : AndroidTask
2424{
25- // Names of assemblies which don't have Mono.Android.dll references, or are framework assemblies, but which must
26- // be scanned for Java types.
27- static readonly HashSet < string > SpecialAssemblies = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) {
28- "Java.Interop.dll" ,
29- "Mono.Android.dll" ,
30- "Mono.Android.Runtime.dll" ,
31- } ;
32-
3325 public override string TaskPrefix => "AMP" ;
3426
3527 public string ApplicationJavaClass { get ; set ; } = "" ;
@@ -66,6 +58,12 @@ public class AssemblyModifierPipeline : AndroidTask
6658 [ Required ]
6759 public ITaskItem [ ] SourceFiles { get ; set ; } = [ ] ;
6860
61+ /// <summary>
62+ /// $(TargetName) would be "AndroidApp1" with no extension
63+ /// </summary>
64+ [ Required ]
65+ public string TargetName { get ; set ; } = "" ;
66+
6967 protected JavaPeerStyle codeGenerationTarget ;
7068
7169 public override bool RunTask ( )
@@ -86,14 +84,14 @@ public override bool RunTask ()
8684
8785 Dictionary < AndroidTargetArch , Dictionary < string , ITaskItem > > perArchAssemblies = MonoAndroidHelper . GetPerArchAssemblies ( ResolvedAssemblies , Array . Empty < string > ( ) , validate : false ) ;
8886
89- RunState ? runState = null ;
87+ AssemblyPipeline ? pipeline = null ;
9088 var currentArch = AndroidTargetArch . None ;
9189
9290 for ( int i = 0 ; i < SourceFiles . Length ; i ++ ) {
9391 ITaskItem source = SourceFiles [ i ] ;
94- AndroidTargetArch sourceArch = GetValidArchitecture ( source ) ;
92+ AndroidTargetArch sourceArch = MonoAndroidHelper . GetRequiredValidArchitecture ( source ) ;
9593 ITaskItem destination = DestinationFiles [ i ] ;
96- AndroidTargetArch destinationArch = GetValidArchitecture ( destination ) ;
94+ AndroidTargetArch destinationArch = MonoAndroidHelper . GetRequiredValidArchitecture ( destination ) ;
9795
9896 if ( sourceArch != destinationArch ) {
9997 throw new InvalidOperationException ( $ "Internal error: assembly '{ sourceArch } ' targets architecture '{ sourceArch } ', while destination assembly '{ destination } ' targets '{ destinationArch } ' instead") ;
@@ -102,147 +100,85 @@ public override bool RunTask ()
102100 // Each architecture must have a different set of context classes, or otherwise only the first instance of the assembly may be rewritten.
103101 if ( currentArch != sourceArch ) {
104102 currentArch = sourceArch ;
105- runState ? . Dispose ( ) ;
103+ pipeline ? . Dispose ( ) ;
106104
107105 var resolver = new DirectoryAssemblyResolver ( this . CreateTaskLogger ( ) , loadDebugSymbols : ReadSymbols , loadReaderParameters : readerParameters ) ;
108- runState = new RunState ( resolver ) ;
109106
110107 // Add SearchDirectories for the current architecture's ResolvedAssemblies
111108 foreach ( var kvp in perArchAssemblies [ sourceArch ] ) {
112109 ITaskItem assembly = kvp . Value ;
113110 var path = Path . GetFullPath ( Path . GetDirectoryName ( assembly . ItemSpec ) ) ;
114- if ( ! runState . resolver . SearchDirectories . Contains ( path ) ) {
115- runState . resolver . SearchDirectories . Add ( path ) ;
111+ if ( ! resolver . SearchDirectories . Contains ( path ) ) {
112+ resolver . SearchDirectories . Add ( path ) ;
116113 }
117114 }
118115
119116 // Set up the FixAbstractMethodsStep and AddKeepAlivesStep
120- var context = new MSBuildLinkContext ( runState . resolver , Log ) ;
117+ var context = new MSBuildLinkContext ( resolver , Log ) ;
118+ pipeline = new AssemblyPipeline ( resolver ) ;
121119
122- CreateRunState ( runState , context ) ;
120+ BuildPipeline ( pipeline , context ) ;
123121 }
124122
125123 Directory . CreateDirectory ( Path . GetDirectoryName ( destination . ItemSpec ) ) ;
126124
127- RunPipeline ( source , destination , runState ! , writerParameters ) ;
125+ RunPipeline ( pipeline ! , source , destination , writerParameters ) ;
128126 }
129127
130- runState ? . Dispose ( ) ;
128+ pipeline ? . Dispose ( ) ;
131129
132130 return ! Log . HasLoggedErrors ;
133131 }
134132
135- protected virtual void CreateRunState ( RunState runState , MSBuildLinkContext context )
133+ protected virtual void BuildPipeline ( AssemblyPipeline pipeline , MSBuildLinkContext context )
136134 {
135+ // FindJavaObjectsStep
137136 var findJavaObjectsStep = new FindJavaObjectsStep ( Log ) {
138137 ApplicationJavaClass = ApplicationJavaClass ,
139138 ErrorOnCustomJavaObject = ErrorOnCustomJavaObject ,
140139 UseMarshalMethods = EnableMarshalMethods ,
141140 } ;
142141
143142 findJavaObjectsStep . Initialize ( context ) ;
144-
145- runState . findJavaObjectsStep = findJavaObjectsStep ;
143+ pipeline . Steps . Add ( findJavaObjectsStep ) ;
146144 }
147145
148- protected virtual void RunPipeline ( ITaskItem source , ITaskItem destination , RunState runState , WriterParameters writerParameters )
146+ void RunPipeline ( AssemblyPipeline pipeline , ITaskItem source , ITaskItem destination , WriterParameters writerParameters )
149147 {
150- var destinationJLOXml = JavaObjectsXmlFile . GetJavaObjectsXmlFilePath ( destination . ItemSpec ) ;
151-
152- if ( ! TryScanForJavaObjects ( source , destination , runState , writerParameters ) ) {
153- // Even if we didn't scan for Java objects, we still write an empty .xml file for later steps
154- JavaObjectsXmlFile . WriteEmptyFile ( destinationJLOXml , Log ) ;
155- }
156- }
157-
158- bool TryScanForJavaObjects ( ITaskItem source , ITaskItem destination , RunState runState , WriterParameters writerParameters )
159- {
160- if ( ! ShouldScanAssembly ( source ) )
161- return false ;
162-
163- var destinationJLOXml = JavaObjectsXmlFile . GetJavaObjectsXmlFilePath ( destination . ItemSpec ) ;
164- var assemblyDefinition = runState . resolver ! . GetAssembly ( source . ItemSpec ) ;
165-
166- var scanned = runState . findJavaObjectsStep ! . ProcessAssembly ( assemblyDefinition , destinationJLOXml ) ;
167-
168- return scanned ;
169- }
170-
171- bool ShouldScanAssembly ( ITaskItem source )
172- {
173- // Skip this assembly if it is not an Android assembly
174- if ( ! IsAndroidAssembly ( source ) ) {
175- Log . LogDebugMessage ( $ "Skipping assembly '{ source . ItemSpec } ' because it is not an Android assembly") ;
176- return false ;
177- }
178-
179- // When marshal methods or non-JavaPeerStyle.XAJavaInterop1 are in use we do not want to skip non-user assemblies (such as Mono.Android) - we need to generate JCWs for them during
180- // application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android
181- // build and stored in a jar file.
182- var useMarshalMethods = ! Debug && EnableMarshalMethods ;
183- var shouldSkipNonUserAssemblies = ! useMarshalMethods && codeGenerationTarget == JavaPeerStyle . XAJavaInterop1 ;
184-
185- if ( shouldSkipNonUserAssemblies && ! ResolvedUserAssemblies . Any ( a => a . ItemSpec == source . ItemSpec ) ) {
186- Log . LogDebugMessage ( $ "Skipping assembly '{ source . ItemSpec } ' because it is not a user assembly and we don't need JLOs from non-user assemblies") ;
187- return false ;
188- }
189-
190- return true ;
191- }
192-
193- bool IsAndroidAssembly ( ITaskItem source )
194- {
195- string name = Path . GetFileName ( source . ItemSpec ) ;
196-
197- if ( SpecialAssemblies . Contains ( name ) )
198- return true ;
148+ var assembly = pipeline . Resolver . GetAssembly ( source . ItemSpec ) ;
149+
150+ var context = new StepContext ( source , destination ) {
151+ CodeGenerationTarget = codeGenerationTarget ,
152+ EnableMarshalMethods = EnableMarshalMethods ,
153+ IsAndroidAssembly = MonoAndroidHelper . IsAndroidAssembly ( source ) ,
154+ IsDebug = Debug ,
155+ IsFrameworkAssembly = MonoAndroidHelper . IsFrameworkAssembly ( source ) ,
156+ IsMainAssembly = Path . GetFileNameWithoutExtension ( source . ItemSpec ) == TargetName ,
157+ IsUserAssembly = ResolvedUserAssemblies . Any ( a => a . ItemSpec == source . ItemSpec ) ,
158+ } ;
199159
200- return MonoAndroidHelper . IsMonoAndroidAssembly ( source ) ;
201- }
160+ var changed = pipeline . Run ( assembly , context ) ;
202161
203- AndroidTargetArch GetValidArchitecture ( ITaskItem item )
204- {
205- AndroidTargetArch ret = MonoAndroidHelper . GetTargetArch ( item ) ;
206- if ( ret == AndroidTargetArch . None ) {
207- throw new InvalidOperationException ( $ "Internal error: assembly '{ item } ' doesn't target any architecture.") ;
162+ if ( changed ) {
163+ Log . LogDebugMessage ( $ "Saving modified assembly: { destination . ItemSpec } ") ;
164+ Directory . CreateDirectory ( Path . GetDirectoryName ( destination . ItemSpec ) ) ;
165+ writerParameters . WriteSymbols = assembly . MainModule . HasSymbols ;
166+ assembly . Write ( destination . ItemSpec , writerParameters ) ;
167+ } else {
168+ // If we didn't write a modified file, copy the original to the destination
169+ CopyIfChanged ( source , destination ) ;
208170 }
209-
210- return ret ;
211171 }
212172
213- protected sealed class RunState : IDisposable
173+ void CopyIfChanged ( ITaskItem source , ITaskItem destination )
214174 {
215- public DirectoryAssemblyResolver resolver ;
216- public FixAbstractMethodsStep ? fixAbstractMethodsStep = null ;
217- public AddKeepAlivesStep ? addKeepAliveStep = null ;
218- public FixLegacyResourceDesignerStep ? fixLegacyResourceDesignerStep = null ;
219- public FindJavaObjectsStep ? findJavaObjectsStep = null ;
220- bool disposed_value ;
221-
222- public RunState ( DirectoryAssemblyResolver resolver )
223- {
224- this . resolver = resolver ;
225- }
226-
227- private void Dispose ( bool disposing )
228- {
229- if ( ! disposed_value ) {
230- if ( disposing ) {
231- resolver ? . Dispose ( ) ;
232- fixAbstractMethodsStep = null ;
233- fixLegacyResourceDesignerStep = null ;
234- addKeepAliveStep = null ;
235- findJavaObjectsStep = null ;
236- }
237- disposed_value = true ;
238- }
239- }
175+ if ( MonoAndroidHelper . CopyAssemblyAndSymbols ( source . ItemSpec , destination . ItemSpec ) ) {
176+ Log . LogDebugMessage ( $ "Copied: { destination . ItemSpec } ") ;
177+ } else {
178+ Log . LogDebugMessage ( $ "Skipped unchanged file: { destination . ItemSpec } ") ;
240179
241- public void Dispose ( )
242- {
243- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
244- Dispose ( disposing : true ) ;
245- GC . SuppressFinalize ( this ) ;
180+ // NOTE: We still need to update the timestamp on this file, or this target would run again
181+ File . SetLastWriteTimeUtc ( destination . ItemSpec , DateTime . UtcNow ) ;
246182 }
247183 }
248184}
0 commit comments