diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs index 570f013e36d..76097843b17 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs @@ -52,7 +52,7 @@ bool CheckShouldProcessAssembly (AssemblyDefinition assembly) return false; #if !NET5_LINKER - CheckAppDomainUsageUnconditional (assembly, (string msg) => Context.LogMessage (MessageImportance.High, msg)); + CheckAppDomainUsage (assembly, (string msg) => Context.LogMessage (MessageImportance.High, msg)); #endif // !NET5_LINKER return assembly.MainModule.HasTypeReference ("Java.Lang.Object"); @@ -86,22 +86,24 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) if (!CheckShouldProcessAssembly (assembly)) return; - if (FixAbstractMethodsUnconditional (assembly)) { + if (FixAbstractMethods (assembly)) { Context.SafeReadSymbols (assembly); UpdateAssemblyAction (assembly); MarkAbstractMethodErrorType (); } } - internal void CheckAppDomainUsage (AssemblyDefinition assembly, Action warn) + internal bool FixAbstractMethods (AssemblyDefinition assembly) { - if (IsProductOrSdkAssembly (assembly)) - return; - - CheckAppDomainUsageUnconditional (assembly, warn); + bool changed = false; + foreach (var type in assembly.MainModule.Types) { + if (MightNeedFix (type)) + changed |= FixAbstractMethods (type); + } + return changed; } - void CheckAppDomainUsageUnconditional (AssemblyDefinition assembly, Action warn) + internal void CheckAppDomainUsage (AssemblyDefinition assembly, Action warn) { if (!assembly.MainModule.HasTypeReference ("System.AppDomain")) return; @@ -113,27 +115,13 @@ void CheckAppDomainUsageUnconditional (AssemblyDefinition assembly, Action + IsProductOrSdkAssembly (assembly.Name.Name); + + public bool IsProductOrSdkAssembly (string assemblyName) => + Profile.IsSdkAssembly (assemblyName) || Profile.IsProductAssembly (assemblyName); bool MightNeedFix (TypeDefinition type) { diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs index 2a5919d8868..dedfe4b87d2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs @@ -1,11 +1,10 @@ +#nullable enable using Java.Interop.Tools.Cecil; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Mono.Cecil; using System; using System.IO; - -using MTProfile = Mono.Tuner.Profile; using Microsoft.Android.Build.Tasks; namespace Xamarin.Android.Tasks @@ -21,13 +20,21 @@ public class LinkAssembliesNoShrink : AndroidTask /// These are used so we have the full list of SearchDirectories /// [Required] - public ITaskItem [] ResolvedAssemblies { get; set; } + public ITaskItem [] ResolvedAssemblies { get; set; } = Array.Empty (); + + [Required] + public ITaskItem [] SourceFiles { get; set; } = Array.Empty (); [Required] - public ITaskItem [] SourceFiles { get; set; } + public ITaskItem [] DestinationFiles { get; set; } = Array.Empty (); + /// + /// $(TargetName) would be "AndroidApp1" with no extension + /// [Required] - public ITaskItem [] DestinationFiles { get; set; } + public string TargetName { get; set; } = ""; + + public bool UsingAndroidNETSdk { get; set; } public bool AddKeepAlives { get; set; } @@ -45,31 +52,38 @@ public override bool RunTask () DeterministicMvid = Deterministic, }; - var hasSystemPrivateCorelib = false; using (var resolver = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: readerParameters)) { // Add SearchDirectories with ResolvedAssemblies foreach (var assembly in ResolvedAssemblies) { var path = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec)); - if (Path.GetFileName (assembly.ItemSpec).Equals ("System.Private.CoreLib.dll", StringComparison.OrdinalIgnoreCase)) - hasSystemPrivateCorelib = true; - if (!resolver.SearchDirectories.Contains (path)) resolver.SearchDirectories.Add (path); } - // Set up the FixAbstractMethodsStep - var step1 = new FixAbstractMethodsStep (resolver, new TypeDefinitionCache (), Log); - // Set up the AddKeepAlivesStep - var step2 = new AddKeepAlivesStep (resolver, new TypeDefinitionCache (), Log, hasSystemPrivateCorelib); + // Set up the FixAbstractMethodsStep and AddKeepAlivesStep + var cache = new TypeDefinitionCache (); + var fixAbstractMethodsStep = new FixAbstractMethodsStep (resolver, cache, Log); + var addKeepAliveStep = new AddKeepAlivesStep (resolver, cache, Log, UsingAndroidNETSdk); for (int i = 0; i < SourceFiles.Length; i++) { var source = SourceFiles [i]; var destination = DestinationFiles [i]; - AssemblyDefinition assemblyDefinition = null; - var assemblyName = Path.GetFileNameWithoutExtension (source.ItemSpec); - if (!MTProfile.IsSdkAssembly (assemblyName) && !MTProfile.IsProductAssembly (assemblyName)) { + + // In .NET 6+, we can skip the main assembly + if (UsingAndroidNETSdk && !AddKeepAlives && assemblyName == TargetName) { + CopyIfChanged (source, destination); + continue; + } + if (fixAbstractMethodsStep.IsProductOrSdkAssembly (assemblyName)) { + CopyIfChanged (source, destination); + continue; + } + + // Check AppDomain usage on any non-Product or Sdk assembly + AssemblyDefinition? assemblyDefinition = null; + if (!UsingAndroidNETSdk) { assemblyDefinition = resolver.GetAssembly (source.ItemSpec); - step1.CheckAppDomainUsage (assemblyDefinition, (string msg) => Log.LogMessageFromText (msg, MessageImportance.High)); + fixAbstractMethodsStep.CheckAppDomainUsage (assemblyDefinition, (string msg) => Log.LogMessageFromText (msg, MessageImportance.High)); } // Only run the step on "MonoAndroid" assemblies @@ -77,8 +91,8 @@ public override bool RunTask () if (assemblyDefinition == null) assemblyDefinition = resolver.GetAssembly (source.ItemSpec); - if (step1.FixAbstractMethods (assemblyDefinition) || - (AddKeepAlives && step2.AddKeepAlives (assemblyDefinition))) { + if (fixAbstractMethodsStep.FixAbstractMethods (assemblyDefinition) || + (AddKeepAlives && addKeepAliveStep.AddKeepAlives (assemblyDefinition))) { Log.LogDebugMessage ($"Saving modified assembly: {destination.ItemSpec}"); writerParameters.WriteSymbols = assemblyDefinition.MainModule.HasSymbols; assemblyDefinition.Write (destination.ItemSpec, writerParameters); @@ -86,20 +100,25 @@ public override bool RunTask () } } - if (MonoAndroidHelper.CopyAssemblyAndSymbols (source.ItemSpec, destination.ItemSpec)) { - Log.LogDebugMessage ($"Copied: {destination.ItemSpec}"); - } else { - Log.LogDebugMessage ($"Skipped unchanged file: {destination.ItemSpec}"); - - // NOTE: We still need to update the timestamp on this file, or this target would run again - File.SetLastWriteTimeUtc (destination.ItemSpec, DateTime.UtcNow); - } + CopyIfChanged (source, destination); } } return !Log.HasLoggedErrors; } + void CopyIfChanged (ITaskItem source, ITaskItem destination) + { + if (MonoAndroidHelper.CopyAssemblyAndSymbols (source.ItemSpec, destination.ItemSpec)) { + Log.LogDebugMessage ($"Copied: {destination.ItemSpec}"); + } else { + Log.LogDebugMessage ($"Skipped unchanged file: {destination.ItemSpec}"); + + // NOTE: We still need to update the timestamp on this file, or this target would run again + File.SetLastWriteTimeUtc (destination.ItemSpec, DateTime.UtcNow); + } + } + class FixAbstractMethodsStep : MonoDroid.Tuner.FixAbstractMethodsStep { readonly DirectoryAssemblyResolver resolver; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs index 5aa231c2636..217c5d2ebaf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs @@ -179,16 +179,34 @@ public void PreserveCustomHttpClientHandlers () } [Test] - [Category ("DotNetIgnore")] // n/a on .NET 5+ public void WarnAboutAppDomains ([Values (true, false)] bool isRelease) { - var proj = new XamarinAndroidApplicationProject () { IsRelease = isRelease }; - proj.MainActivity = proj.DefaultMainActivity.Replace ("base.OnCreate (bundle);", "base.OnCreate (bundle);\nvar appDomain = System.AppDomain.CreateDomain (\"myDomain\");"); - using (var b = CreateApkBuilder ()) { - Assert.IsTrue (b.Build (proj), "Build should have succeeded."); - Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "2 Warning(s)"), "MSBuild should count 2 warnings."); - Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "warning CS0618: 'AppDomain.CreateDomain(string)' is obsolete: 'AppDomain.CreateDomain will no longer be supported in .NET 5 and later."), "Should warn CS0618 about creating AppDomain."); - Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "warning XA2000: Use of AppDomain.CreateDomain()"), "Should warn XA2000 about creating AppDomain."); + var path = Path.Combine (Root, "temp", TestName); + var lib = new XamarinAndroidLibraryProject { + IsRelease = isRelease, + ProjectName = "Library", + Sources = { + new BuildItem.Source ("Foo.cs") { + TextContent = () => "class Foo { System.AppDomain Bar => System.AppDomain.CreateDomain (\"myDomain\"); }", + } + } + }; + + var app = new XamarinAndroidApplicationProject { IsRelease = isRelease }; + app.AddReference (lib); + using var libBuilder = CreateDllBuilder (Path.Combine (path, lib.ProjectName)); + Assert.IsTrue (libBuilder.Build (lib), "library build should have succeeded."); + // AppDomain.CreateDomain() is [Obsolete] + Assert.IsTrue (StringAssertEx.ContainsText (libBuilder.LastBuildOutput, "1 Warning(s)"), "MSBuild should count 1 warnings."); + + using var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName)); + Assert.IsTrue (appBuilder.Build (app), "app build should have succeeded."); + + if (Builder.UseDotNet) { + appBuilder.AssertHasNoWarnings (); + } else { + Assert.IsTrue (StringAssertEx.ContainsText (appBuilder.LastBuildOutput, "1 Warning(s)"), "MSBuild should count 1 warnings."); + Assert.IsTrue (StringAssertEx.ContainsText (appBuilder.LastBuildOutput, "warning XA2000: Use of AppDomain.CreateDomain()"), "Should warn XA2000 about creating AppDomain."); } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 36f58a74f77..beb49fc4302 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1373,8 +1373,10 @@ because xbuild doesn't support framework reference assemblies. ResolvedAssemblies="@(_AllResolvedAssemblies)" SourceFiles="@(ResolvedAssemblies)" DestinationFiles="@(ResolvedAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')" + TargetName="$(TargetName)" AddKeepAlives="$(AndroidAddKeepAlives)" Deterministic="$(Deterministic)" + UsingAndroidNETSdk="$(UsingAndroidNETSdk)" />