-
Notifications
You must be signed in to change notification settings - Fork 564
[XABT] Refactor <LinkAssembliesNoShrink>/<AssemblyModifierPipeline> tasks to use an actual pipeline.
#10024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[XABT] Refactor <LinkAssembliesNoShrink>/<AssemblyModifierPipeline> tasks to use an actual pipeline.
#10024
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,9 @@ | |
| namespace MonoDroid.Tuner | ||
| { | ||
| public class AddKeepAlivesStep : BaseStep | ||
| #if !ILLINK | ||
| , IAssemblyModifierPipelineStep | ||
| #endif // !ILLINK | ||
| { | ||
|
|
||
| protected override void ProcessAssembly (AssemblyDefinition assembly) | ||
|
|
@@ -25,6 +28,17 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) | |
| } | ||
| } | ||
|
|
||
| #if !ILLINK | ||
| public bool ProcessAssembly (AssemblyDefinition assembly, StepContext context) | ||
| { | ||
| // Only run this step on user Android assemblies | ||
| if (!context.IsAndroidUserAssembly) | ||
| return false; | ||
|
|
||
| return AddKeepAlives (assembly); | ||
| } | ||
| #endif // !ILLINK | ||
|
Comment on lines
+31
to
+40
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, maybe we could lose the |
||
|
|
||
| internal bool AddKeepAlives (AssemblyDefinition assembly) | ||
| { | ||
| if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,14 +22,6 @@ namespace Xamarin.Android.Tasks; | |
| /// </summary> | ||
| public class AssemblyModifierPipeline : AndroidTask | ||
| { | ||
| // Names of assemblies which don't have Mono.Android.dll references, or are framework assemblies, but which must | ||
| // be scanned for Java types. | ||
| static readonly HashSet<string> SpecialAssemblies = new HashSet<string> (StringComparer.OrdinalIgnoreCase) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How many "copies" of this list do we have?! See also
The ones that "don't match" are
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The unsatisfying answer is I simply moved the existing logic.😉
Maybe? We would likely need to audit all users of both lists to see what their intentions are.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unified the lists and CI isn't complaining, so I guess that's good enough! 🤷♂️ |
||
| "Java.Interop.dll", | ||
| "Mono.Android.dll", | ||
| "Mono.Android.Runtime.dll", | ||
| }; | ||
|
|
||
| public override string TaskPrefix => "AMP"; | ||
|
|
||
| public string ApplicationJavaClass { get; set; } = ""; | ||
|
|
@@ -66,6 +58,12 @@ public class AssemblyModifierPipeline : AndroidTask | |
| [Required] | ||
| public ITaskItem [] SourceFiles { get; set; } = []; | ||
|
|
||
| /// <summary> | ||
| /// $(TargetName) would be "AndroidApp1" with no extension | ||
| /// </summary> | ||
| [Required] | ||
| public string TargetName { get; set; } = ""; | ||
|
|
||
| protected JavaPeerStyle codeGenerationTarget; | ||
|
|
||
| public override bool RunTask () | ||
|
|
@@ -86,14 +84,14 @@ public override bool RunTask () | |
|
|
||
| Dictionary<AndroidTargetArch, Dictionary<string, ITaskItem>> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (ResolvedAssemblies, Array.Empty<string> (), validate: false); | ||
|
|
||
| RunState? runState = null; | ||
| AssemblyPipeline? pipeline = null; | ||
| var currentArch = AndroidTargetArch.None; | ||
|
|
||
| for (int i = 0; i < SourceFiles.Length; i++) { | ||
| ITaskItem source = SourceFiles [i]; | ||
| AndroidTargetArch sourceArch = GetValidArchitecture (source); | ||
| AndroidTargetArch sourceArch = MonoAndroidHelper.GetRequiredValidArchitecture (source); | ||
| ITaskItem destination = DestinationFiles [i]; | ||
| AndroidTargetArch destinationArch = GetValidArchitecture (destination); | ||
| AndroidTargetArch destinationArch = MonoAndroidHelper.GetRequiredValidArchitecture (destination); | ||
|
|
||
| if (sourceArch != destinationArch) { | ||
| 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 () | |
| // Each architecture must have a different set of context classes, or otherwise only the first instance of the assembly may be rewritten. | ||
| if (currentArch != sourceArch) { | ||
| currentArch = sourceArch; | ||
| runState?.Dispose (); | ||
| pipeline?.Dispose (); | ||
|
|
||
| var resolver = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: ReadSymbols, loadReaderParameters: readerParameters); | ||
| runState = new RunState (resolver); | ||
|
|
||
| // Add SearchDirectories for the current architecture's ResolvedAssemblies | ||
| foreach (var kvp in perArchAssemblies [sourceArch]) { | ||
| ITaskItem assembly = kvp.Value; | ||
| var path = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec)); | ||
| if (!runState.resolver.SearchDirectories.Contains (path)) { | ||
| runState.resolver.SearchDirectories.Add (path); | ||
| if (!resolver.SearchDirectories.Contains (path)) { | ||
| resolver.SearchDirectories.Add (path); | ||
| } | ||
| } | ||
|
|
||
| // Set up the FixAbstractMethodsStep and AddKeepAlivesStep | ||
| var context = new MSBuildLinkContext (runState.resolver, Log); | ||
| var context = new MSBuildLinkContext (resolver, Log); | ||
| pipeline = new AssemblyPipeline (resolver); | ||
|
|
||
| CreateRunState (runState, context); | ||
| BuildPipeline (pipeline, context); | ||
| } | ||
|
|
||
| Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec)); | ||
|
|
||
| RunPipeline (source, destination, runState!, writerParameters); | ||
| RunPipeline (pipeline!, source, destination, writerParameters); | ||
| } | ||
|
|
||
| runState?.Dispose (); | ||
| pipeline?.Dispose (); | ||
|
|
||
| return !Log.HasLoggedErrors; | ||
| } | ||
|
|
||
| protected virtual void CreateRunState (RunState runState, MSBuildLinkContext context) | ||
| protected virtual void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkContext context) | ||
| { | ||
| // FindJavaObjectsStep | ||
| var findJavaObjectsStep = new FindJavaObjectsStep (Log) { | ||
| ApplicationJavaClass = ApplicationJavaClass, | ||
| ErrorOnCustomJavaObject = ErrorOnCustomJavaObject, | ||
| UseMarshalMethods = EnableMarshalMethods, | ||
| }; | ||
|
|
||
| findJavaObjectsStep.Initialize (context); | ||
|
|
||
| runState.findJavaObjectsStep = findJavaObjectsStep; | ||
| pipeline.Steps.Add (findJavaObjectsStep); | ||
| } | ||
|
|
||
| protected virtual void RunPipeline (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters) | ||
| void RunPipeline (AssemblyPipeline pipeline, ITaskItem source, ITaskItem destination, WriterParameters writerParameters) | ||
| { | ||
| var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec); | ||
|
|
||
| if (!TryScanForJavaObjects (source, destination, runState, writerParameters)) { | ||
| // Even if we didn't scan for Java objects, we still write an empty .xml file for later steps | ||
| JavaObjectsXmlFile.WriteEmptyFile (destinationJLOXml, Log); | ||
| } | ||
| } | ||
|
|
||
| bool TryScanForJavaObjects (ITaskItem source, ITaskItem destination, RunState runState, WriterParameters writerParameters) | ||
| { | ||
| if (!ShouldScanAssembly (source)) | ||
| return false; | ||
|
|
||
| var destinationJLOXml = JavaObjectsXmlFile.GetJavaObjectsXmlFilePath (destination.ItemSpec); | ||
| var assemblyDefinition = runState.resolver!.GetAssembly (source.ItemSpec); | ||
|
|
||
| var scanned = runState.findJavaObjectsStep!.ProcessAssembly (assemblyDefinition, destinationJLOXml); | ||
|
|
||
| return scanned; | ||
| } | ||
|
|
||
| bool ShouldScanAssembly (ITaskItem source) | ||
| { | ||
| // Skip this assembly if it is not an Android assembly | ||
| if (!IsAndroidAssembly (source)) { | ||
| Log.LogDebugMessage ($"Skipping assembly '{source.ItemSpec}' because it is not an Android assembly"); | ||
| return false; | ||
| } | ||
|
|
||
| // 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 | ||
| // application build, unlike in Debug configuration or when marshal methods are disabled, in which case we use JCWs generated during Xamarin.Android | ||
| // build and stored in a jar file. | ||
| var useMarshalMethods = !Debug && EnableMarshalMethods; | ||
| var shouldSkipNonUserAssemblies = !useMarshalMethods && codeGenerationTarget == JavaPeerStyle.XAJavaInterop1; | ||
|
|
||
| if (shouldSkipNonUserAssemblies && !ResolvedUserAssemblies.Any (a => a.ItemSpec == source.ItemSpec)) { | ||
| Log.LogDebugMessage ($"Skipping assembly '{source.ItemSpec}' because it is not a user assembly and we don't need JLOs from non-user assemblies"); | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| bool IsAndroidAssembly (ITaskItem source) | ||
| { | ||
| string name = Path.GetFileName (source.ItemSpec); | ||
|
|
||
| if (SpecialAssemblies.Contains (name)) | ||
| return true; | ||
| var assembly = pipeline.Resolver.GetAssembly (source.ItemSpec); | ||
|
|
||
| var context = new StepContext (source, destination) { | ||
| CodeGenerationTarget = codeGenerationTarget, | ||
| EnableMarshalMethods = EnableMarshalMethods, | ||
| IsAndroidAssembly = MonoAndroidHelper.IsAndroidAssembly (source), | ||
| IsDebug = Debug, | ||
| IsFrameworkAssembly = MonoAndroidHelper.IsFrameworkAssembly (source), | ||
| IsMainAssembly = Path.GetFileNameWithoutExtension (source.ItemSpec) == TargetName, | ||
| IsUserAssembly = ResolvedUserAssemblies.Any (a => a.ItemSpec == source.ItemSpec), | ||
| }; | ||
|
|
||
| return MonoAndroidHelper.IsMonoAndroidAssembly (source); | ||
| } | ||
| var changed = pipeline.Run (assembly, context); | ||
|
|
||
| AndroidTargetArch GetValidArchitecture (ITaskItem item) | ||
| { | ||
| AndroidTargetArch ret = MonoAndroidHelper.GetTargetArch (item); | ||
| if (ret == AndroidTargetArch.None) { | ||
| throw new InvalidOperationException ($"Internal error: assembly '{item}' doesn't target any architecture."); | ||
| if (changed) { | ||
| Log.LogDebugMessage ($"Saving modified assembly: {destination.ItemSpec}"); | ||
| Directory.CreateDirectory (Path.GetDirectoryName (destination.ItemSpec)); | ||
| writerParameters.WriteSymbols = assembly.MainModule.HasSymbols; | ||
| assembly.Write (destination.ItemSpec, writerParameters); | ||
| } else { | ||
| // If we didn't write a modified file, copy the original to the destination | ||
| CopyIfChanged (source, destination); | ||
| } | ||
|
|
||
| return ret; | ||
| } | ||
|
|
||
| protected sealed class RunState : IDisposable | ||
| void CopyIfChanged (ITaskItem source, ITaskItem destination) | ||
| { | ||
| public DirectoryAssemblyResolver resolver; | ||
| public FixAbstractMethodsStep? fixAbstractMethodsStep = null; | ||
| public AddKeepAlivesStep? addKeepAliveStep = null; | ||
| public FixLegacyResourceDesignerStep? fixLegacyResourceDesignerStep = null; | ||
| public FindJavaObjectsStep? findJavaObjectsStep = null; | ||
| bool disposed_value; | ||
|
|
||
| public RunState (DirectoryAssemblyResolver resolver) | ||
| { | ||
| this.resolver = resolver; | ||
| } | ||
|
|
||
| private void Dispose (bool disposing) | ||
| { | ||
| if (!disposed_value) { | ||
| if (disposing) { | ||
| resolver?.Dispose (); | ||
| fixAbstractMethodsStep = null; | ||
| fixLegacyResourceDesignerStep = null; | ||
| addKeepAliveStep = null; | ||
| findJavaObjectsStep = null; | ||
| } | ||
| disposed_value = true; | ||
| } | ||
| } | ||
| if (MonoAndroidHelper.CopyAssemblyAndSymbols (source.ItemSpec, destination.ItemSpec)) { | ||
| Log.LogDebugMessage ($"Copied: {destination.ItemSpec}"); | ||
| } else { | ||
| Log.LogDebugMessage ($"Skipped unchanged file: {destination.ItemSpec}"); | ||
|
|
||
| public void Dispose () | ||
| { | ||
| // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method | ||
| Dispose (disposing: true); | ||
| GC.SuppressFinalize (this); | ||
| // NOTE: We still need to update the timestamp on this file, or this target would run again | ||
| File.SetLastWriteTimeUtc (destination.ItemSpec, DateTime.UtcNow); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
src\Microsoft.Android.Sdk.ILLink\Microsoft.Android.Sdk.ILLink.csprojhadIAssemblyModifierPipelineStep.cswe could drop the#if?