@@ -20,16 +20,8 @@ namespace Xamarin.Android.Tasks;
20
20
/// are run *after* the linker has run. Additionally, this task is run by
21
21
/// LinkAssembliesNoShrink to modify assemblies when ILLink is not used.
22
22
/// </summary>
23
- public class AssemblyModifierPipeline : AndroidTask
23
+ public partial class AssemblyModifierPipeline : AndroidTask
24
24
{
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
-
33
25
public override string TaskPrefix => "AMP" ;
34
26
35
27
public string ApplicationJavaClass { get ; set ; } = "" ;
@@ -66,6 +58,12 @@ public class AssemblyModifierPipeline : AndroidTask
66
58
[ Required ]
67
59
public ITaskItem [ ] SourceFiles { get ; set ; } = [ ] ;
68
60
61
+ /// <summary>
62
+ /// $(TargetName) would be "AndroidApp1" with no extension
63
+ /// </summary>
64
+ [ Required ]
65
+ public string TargetName { get ; set ; } = "" ;
66
+
69
67
protected JavaPeerStyle codeGenerationTarget ;
70
68
71
69
public override bool RunTask ( )
@@ -86,14 +84,14 @@ public override bool RunTask ()
86
84
87
85
Dictionary < AndroidTargetArch , Dictionary < string , ITaskItem > > perArchAssemblies = MonoAndroidHelper . GetPerArchAssemblies ( ResolvedAssemblies , Array . Empty < string > ( ) , validate : false ) ;
88
86
89
- RunState ? runState = null ;
87
+ AssemblyPipeline ? pipeline = null ;
90
88
var currentArch = AndroidTargetArch . None ;
91
89
92
90
for ( int i = 0 ; i < SourceFiles . Length ; i ++ ) {
93
91
ITaskItem source = SourceFiles [ i ] ;
94
- AndroidTargetArch sourceArch = GetValidArchitecture ( source ) ;
92
+ AndroidTargetArch sourceArch = MonoAndroidHelper . GetRequiredValidArchitecture ( source ) ;
95
93
ITaskItem destination = DestinationFiles [ i ] ;
96
- AndroidTargetArch destinationArch = GetValidArchitecture ( destination ) ;
94
+ AndroidTargetArch destinationArch = MonoAndroidHelper . GetRequiredValidArchitecture ( destination ) ;
97
95
98
96
if ( sourceArch != destinationArch ) {
99
97
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 ()
102
100
// Each architecture must have a different set of context classes, or otherwise only the first instance of the assembly may be rewritten.
103
101
if ( currentArch != sourceArch ) {
104
102
currentArch = sourceArch ;
105
- runState ? . Dispose ( ) ;
103
+ pipeline ? . Dispose ( ) ;
106
104
107
105
var resolver = new DirectoryAssemblyResolver ( this . CreateTaskLogger ( ) , loadDebugSymbols : ReadSymbols , loadReaderParameters : readerParameters ) ;
108
- runState = new RunState ( resolver ) ;
109
106
110
107
// Add SearchDirectories for the current architecture's ResolvedAssemblies
111
108
foreach ( var kvp in perArchAssemblies [ sourceArch ] ) {
112
109
ITaskItem assembly = kvp . Value ;
113
110
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 ) ;
116
113
}
117
114
}
118
115
119
116
// 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 ) ;
121
119
122
- CreateRunState ( runState , context ) ;
120
+ BuildPipeline ( pipeline , context ) ;
123
121
}
124
122
125
123
Directory . CreateDirectory ( Path . GetDirectoryName ( destination . ItemSpec ) ) ;
126
124
127
- RunPipeline ( source , destination , runState ! , writerParameters ) ;
125
+ RunPipeline ( pipeline ! , source , destination , writerParameters ) ;
128
126
}
129
127
130
- runState ? . Dispose ( ) ;
128
+ pipeline ? . Dispose ( ) ;
131
129
132
130
return ! Log . HasLoggedErrors ;
133
131
}
134
132
135
- protected virtual void CreateRunState ( RunState runState , MSBuildLinkContext context )
133
+ protected virtual void BuildPipeline ( AssemblyPipeline pipeline , MSBuildLinkContext context )
136
134
{
135
+ // FindJavaObjectsStep
137
136
var findJavaObjectsStep = new FindJavaObjectsStep ( Log ) {
138
137
ApplicationJavaClass = ApplicationJavaClass ,
139
138
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject ,
140
139
UseMarshalMethods = EnableMarshalMethods ,
141
140
} ;
142
141
143
142
findJavaObjectsStep . Initialize ( context ) ;
144
-
145
- runState . findJavaObjectsStep = findJavaObjectsStep ;
143
+ pipeline . Steps . Add ( findJavaObjectsStep ) ;
146
144
}
147
145
148
- protected virtual void RunPipeline ( ITaskItem source , ITaskItem destination , RunState runState , WriterParameters writerParameters )
146
+ void RunPipeline ( AssemblyPipeline pipeline , ITaskItem source , ITaskItem destination , WriterParameters writerParameters )
149
147
{
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
+ } ;
199
159
200
- return MonoAndroidHelper . IsMonoAndroidAssembly ( source ) ;
201
- }
160
+ var changed = pipeline . Run ( assembly , context ) ;
202
161
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 ) ;
208
170
}
209
-
210
- return ret ;
211
171
}
212
172
213
- protected sealed class RunState : IDisposable
173
+ void CopyIfChanged ( ITaskItem source , ITaskItem destination )
214
174
{
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 } ") ;
240
179
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 ) ;
246
182
}
247
183
}
248
184
}
0 commit comments