Skip to content

Commit 804f66e

Browse files
committed
Address review feedback.
1 parent d533b22 commit 804f66e

File tree

8 files changed

+294
-238
lines changed

8 files changed

+294
-238
lines changed

src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FindJavaObjectsStep.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ public bool ProcessAssembly (AssemblyDefinition assembly, string destinationJLOX
4545
Log.LogDebugMessage ($"{assembly.Name.Name} - Found {initial_count} Java types, filtered to {types.Count}");
4646

4747
var wrappers = ConvertToCallableWrappers (types);
48-
XmlExporter.Export (destinationJLOXml, wrappers, true);
48+
49+
using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
50+
XmlExporter.Export (sw, wrappers, true);
51+
Files.CopyIfStreamChanged (sw.BaseStream, destinationJLOXml);
52+
}
4953

5054
return true;
5155
}

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ _ResolveAssemblies MSBuild target.
197197
_ResolveSatellitePaths;
198198
_CreatePackageWorkspace;
199199
_LinkAssemblies;
200-
_LinkAssembliesAdditionalSteps;
200+
_AfterILLinkAdditionalSteps;
201201
</_PrepareAssembliesDependsOnTargets>
202202
</PropertyGroup>
203203

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ properties that determine build ordering.
6767
_ResolveSatellitePaths;
6868
_CreatePackageWorkspace;
6969
_LinkAssemblies;
70-
_LinkAssembliesAdditionalSteps;
70+
_AfterILLinkAdditionalSteps;
7171
_GenerateJavaStubs;
7272
_ManifestMerger;
7373
_ConvertCustomView;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Linq;
6+
using Java.Interop.Tools.Cecil;
7+
using Java.Interop.Tools.JavaCallableWrappers;
8+
using Java.Interop.Tools.TypeNameMappings;
9+
using Microsoft.Android.Build.Tasks;
10+
using Microsoft.Build.Framework;
11+
using Mono.Cecil;
12+
using MonoDroid.Tuner;
13+
using Xamarin.Android.Tools;
14+
using PackageNamingPolicyEnum = Java.Interop.Tools.TypeNameMappings.PackageNamingPolicy;
15+
16+
namespace Xamarin.Android.Tasks;
17+
18+
/// <summary>
19+
/// This task runs additional "linker steps" that are not part of ILLink. These steps
20+
/// are run *after* the linker has run. Additionally, this task is run by
21+
/// LinkAssembliesNoShrink to modify assemblies when ILLink is not used.
22+
/// </summary>
23+
public class AssemblyModifierPipeline : AndroidTask
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+
public override string TaskPrefix => "AMP";
34+
35+
public string ApplicationJavaClass { get; set; } = "";
36+
37+
public string CodeGenerationTarget { get; set; } = "";
38+
39+
public bool Debug { get; set; }
40+
41+
[Required]
42+
public ITaskItem [] DestinationFiles { get; set; } = [];
43+
44+
public bool Deterministic { get; set; }
45+
46+
public bool EnableMarshalMethods { get; set; }
47+
48+
public bool ErrorOnCustomJavaObject { get; set; }
49+
50+
public string? PackageNamingPolicy { get; set; }
51+
52+
/// <summary>
53+
/// Defaults to false, enables Mono.Cecil to load symbols
54+
/// </summary>
55+
public bool ReadSymbols { get; set; }
56+
57+
/// <summary>
58+
/// These are used so we have the full list of SearchDirectories
59+
/// </summary>
60+
[Required]
61+
public ITaskItem [] ResolvedAssemblies { get; set; } = [];
62+
63+
[Required]
64+
public ITaskItem [] ResolvedUserAssemblies { get; set; } = [];
65+
66+
[Required]
67+
public ITaskItem [] SourceFiles { get; set; } = [];
68+
69+
protected JavaPeerStyle codeGenerationTarget;
70+
71+
public override bool RunTask ()
72+
{
73+
codeGenerationTarget = MonoAndroidHelper.ParseCodeGenerationTarget (CodeGenerationTarget);
74+
JavaNativeTypeManager.PackageNamingPolicy = Enum.TryParse (PackageNamingPolicy, out PackageNamingPolicyEnum pnp) ? pnp : PackageNamingPolicyEnum.LowercaseCrc64;
75+
76+
if (SourceFiles.Length != DestinationFiles.Length)
77+
throw new ArgumentException ("source and destination count mismatch");
78+
79+
var readerParameters = new ReaderParameters {
80+
ReadSymbols = ReadSymbols,
81+
};
82+
83+
var writerParameters = new WriterParameters {
84+
DeterministicMvid = Deterministic,
85+
};
86+
87+
Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, Array.Empty<string> (), validate: false);
88+
89+
RunState? runState = null;
90+
var currentArch = AndroidTargetArch.None;
91+
92+
for (int i = 0; i < SourceFiles.Length; i++) {
93+
ITaskItem source = SourceFiles [i];
94+
AndroidTargetArch sourceArch = GetValidArchitecture (source);
95+
ITaskItem destination = DestinationFiles [i];
96+
AndroidTargetArch destinationArch = GetValidArchitecture (destination);
97+
98+
if (sourceArch != destinationArch) {
99+
throw new InvalidOperationException ($"Internal error: assembly '{sourceArch}' targets architecture '{sourceArch}', while destination assembly '{destination}' targets '{destinationArch}' instead");
100+
}
101+
102+
// Each architecture must have a different set of context classes, or otherwise only the first instance of the assembly may be rewritten.
103+
if (currentArch != sourceArch) {
104+
currentArch = sourceArch;
105+
runState?.Dispose ();
106+
107+
var resolver = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: ReadSymbols, loadReaderParameters: readerParameters);
108+
runState = new RunState (resolver);
109+
110+
// Add SearchDirectories for the current architecture's ResolvedAssemblies
111+
foreach (var kvp in perArchAssemblies [sourceArch]) {
112+
ITaskItem assembly = kvp.Value;
113+
var path = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec));
114+
if (!runState.resolver.SearchDirectories.Contains (path)) {
115+
runState.resolver.SearchDirectories.Add (path);
116+
}
117+
}
118+
119+
// Set up the FixAbstractMethodsStep and AddKeepAlivesStep
120+
var context = new MSBuildLinkContext (runState.resolver, Log);
121+
122+
CreateRunState (runState, context);
123+
}
124+
125+
Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec));
126+
127+
RunPipeline (source, destination, runState!, writerParameters);
128+
}
129+
130+
runState?.Dispose ();
131+
132+
return !Log.HasLoggedErrors;
133+
}
134+
135+
protected virtual void CreateRunState (RunState runState, MSBuildLinkContext context)
136+
{
137+
var findJavaObjectsStep = new FindJavaObjectsStep (Log) {
138+
ApplicationJavaClass = ApplicationJavaClass,
139+
ErrorOnCustomJavaObject = ErrorOnCustomJavaObject,
140+
UseMarshalMethods = EnableMarshalMethods,
141+
};
142+
143+
findJavaObjectsStep.Initialize (context);
144+
145+
runState.findJavaObjectsStep = findJavaObjectsStep;
146+
}
147+
148+
protected virtual void RunPipeline (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters)
149+
{
150+
var destinationJLOXml = Path.ChangeExtension (destination.ItemSpec, ".jlo.xml");
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+
FindJavaObjectsStep.WriteEmptyXmlFile (destinationJLOXml);
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 = Path.ChangeExtension (destination.ItemSpec, ".jlo.xml");
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;
199+
200+
return MonoAndroidHelper.IsMonoAndroidAssembly (source);
201+
}
202+
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.");
208+
}
209+
210+
return ret;
211+
}
212+
213+
protected sealed class RunState : IDisposable
214+
{
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+
}
240+
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);
246+
}
247+
}
248+
}

0 commit comments

Comments
 (0)