From 3ae293fa533e76a35ac04ffe55aeb8418b84cdbc Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 10 Apr 2019 23:47:35 -0500 Subject: [PATCH] [Xamarin.Android.Build.Tasks] support missing TFI Context: https://www.nuget.org/packages/Refractored.Controls.CircleImageView/ Context: https://github.com/Azure-Samples/MyDriving The MyDriving sample app currently fails to build on master with: Resources/layout/fragment_profile.axml(2): error APT0000: attribute civ_border_width (aka com.microsoft.mydriving:civ_border_width) not found. The failure happens with both `aapt` and `aapt2`. This layout is using a custom view such as: ... ... This comes from the `Refractored.Controls.CircleImageView` NuGet package. In 5ec3e3a, I added a `` MSBuild task that appears to be to blame. It is not returning `Refractored.Controls.CircleImageView.dll`, but it needs to! `Refractored.Controls.CircleImageView.dll` has no `[assembly: System.Runtime.Versioning.TargetFrameworkAttribute]`... // C:\src\des\MyDriving\packages\Refractored.Controls.CircleImageView.1.0.1\lib\MonoAndroid10\Refractored.Controls.CircleImageView.dll // Refractored.Controls.CircleImageView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // Global type: // Architecture: x86 // Runtime: v4.0.30319 // Hash algorithm: SHA1 using Android.Runtime; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; [assembly: AssemblyTitle("Refractored.Controls.CircleImageView")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] [assembly: AssemblyCopyright("2015 Refractored LLC/James Montemagno")] [assembly: AssemblyTrademark("")] [assembly: NamespaceMapping(Java = "de.hdodenhof.circleimageview", Managed = "Refractored.Controls")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: SecurityPermission(8, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] It is indeed a `MonoAndroid` assembly, since it references `Mono.Android.dll`. It is weird, though... ~~ What should we do? ~~ One idea is to look for `Mono.Android.dll` references in each assembly *instead* of `TargetFrameworkIdentifier`. Looking at the assemblies in Xamarin.Forms, there is a complication to this: * `Xamarin.Forms.Core.dll` (a NetStandard library) references * `Xamarin.Forms.Platform.dll` (a MonoAndroid library?) references * `Xamarin.Forms.Platform.Android.dll` But `Xamarin.Forms.Platform.dll` does not reference `Mono.Android.dll`? So then should we "recursively" look for any reference to `Mono.Android.dll` in a given dependency tree? I don't think so? This would include more assemblies than we want... In the above example `Xamarin.Forms.Core.dll` would get counted as a "MonoAndroid" assembly. ~~ Conclusion ~~ I think we should stick with `TargetFrameworkIdentifier`, and in the rare case it is missing look for a `Mono.Android.dll` reference for the single assembly. This seems like it is going to cover all cases to me, and we still will get good performance. So we will cover: * `Xamarin.Forms.Platform.dll` counted as a "MonoAndroid" assembly since it has a `TargetFrameworkIdentifier` (but no reference). * `Refractored.Controls.CircleImageView.dll` counted as a "MonoAndroid" assembly. It has no `TargetFrameworkIdentifier, but has a reference to `Mono.Android.dll`. Changes include: * `` needs to also check for an assembly reference to `Mono.Android` as a fallback. * `` now adds a `%(MonoAndroidReference)=True` item metadata. * When creating the `@(_ResolvedUserMonoAndroidAssemblies)` item group, we also check for `%(MonoAndroidReference)=True`. I added a few tests to verify these scenarios. --- .../Tasks/FilterAssemblies.cs | 23 +++- .../Tasks/ResolveAssemblies.cs | 16 ++- .../Xamarin.Android.Build.Tests/BuildTest.cs | 30 ++++++ .../Tasks/FilterAssembliesTests.cs | 102 ++++++++++++++++++ .../Xamarin.Android.Build.Tests.csproj | 2 + .../Android/KnownPackages.cs | 10 ++ .../Xamarin.Android.Common.targets | 3 +- 7 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/FilterAssembliesTests.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs index af76d46970f..d024dad5683 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs @@ -8,13 +8,21 @@ namespace Xamarin.Android.Tasks { /// - /// Filters a set of assemblies based on a given TargetFrameworkIdentifier + /// Filters a set of assemblies based on a given TargetFrameworkIdentifier or FallbackReference /// public class FilterAssemblies : Task { + /// + /// The MonoAndroid portion of [assembly: System.Runtime.Versioning.TargetFramework("MonoAndroid,v9.0")] + /// [Required] public string TargetFrameworkIdentifier { get; set; } + /// + /// If TargetFrameworkIdentifier is missing, we can look for Mono.Android.dll references instead + /// + public string FallbackReference { get; set; } + [Required] public bool DesignTimeBuild { get; set; } @@ -40,6 +48,19 @@ public override bool Execute () var targetFrameworkIdentifier = assemblyDefinition.GetTargetFrameworkIdentifier (reader); if (targetFrameworkIdentifier == TargetFrameworkIdentifier) { output.Add (assemblyItem); + continue; + } + // Fallback to looking at references + if (string.IsNullOrEmpty (targetFrameworkIdentifier) && !string.IsNullOrEmpty (FallbackReference)) { + Log.LogDebugMessage ($"Checking references for: {assemblyItem.ItemSpec}"); + foreach (var handle in reader.AssemblyReferences) { + var reference = reader.GetAssemblyReference (handle); + var name = reader.GetString (reference.Name); + if (FallbackReference == name) { + output.Add (assemblyItem); + break; + } + } } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAssemblies.cs index fdadf511586..4410faf952a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAssemblies.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAssemblies.cs @@ -223,14 +223,16 @@ void AddAssemblyReferences (MetadataResolver resolver, Dictionary + /// This assembly weirdly has no [assembly: System.Runtime.Versioning.TargetFrameworkAttribute()], at all... + /// + [Test] + public void AssemblyWithMissingTargetFramework () + { + var proj = new XamarinFormsAndroidApplicationProject { + AndroidResources = { + new AndroidItem.AndroidResource ("Resources\\layout\\test.axml") { + TextContent = () => +@" + + +" + } + } + }; + proj.PackageReferences.Add (KnownPackages.CircleImageView); + using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) { + Assert.IsTrue (b.Build (proj), "build should have succeeded."); + + // We should have a java stub + var intermediate = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); + var javaStub = Path.Combine (intermediate, "android", "src", "md54908d67eb9afef4acc92753cc61471e9", "CircleImageView.java"); + FileAssert.Exists (javaStub); + } + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/FilterAssembliesTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/FilterAssembliesTests.cs new file mode 100644 index 00000000000..a75c14750e6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/FilterAssembliesTests.cs @@ -0,0 +1,102 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Xamarin.Android.Tasks; +using Xamarin.Tools.Zip; +using TaskItem = Microsoft.Build.Utilities.TaskItem; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + public class FilterAssembliesTests : BaseTest + { + HttpClient httpClient = new HttpClient (); + string tempDirectory; + + [SetUp] + public void Setup () + { + tempDirectory = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ()); + Directory.CreateDirectory (tempDirectory); + } + + [TearDown] + public void TearDown () + { + Directory.Delete (tempDirectory, recursive: true); + } + + async Task DownloadFromNuGet (string url) + { + var response = await httpClient.GetAsync (url); + response.EnsureSuccessStatusCode (); + var temp = Path.Combine (tempDirectory, Path.GetRandomFileName ()); + using (var httpStream = await response.Content.ReadAsStreamAsync ()) + using (var fileStream = File.Create (temp)) { + await httpStream.CopyToAsync (fileStream); + } + return temp; + } + + async Task GetAssembliesFromNuGet (string url, string path) + { + var assemblies = new List (); + var nuget = await DownloadFromNuGet (url); + using (var zip = ZipArchive.Open (nuget, FileMode.Open)) { + foreach (var entry in zip) { + if (entry.FullName.StartsWith (path, StringComparison.OrdinalIgnoreCase) && + entry.FullName.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) { + var temp = Path.Combine (tempDirectory, Path.GetFileName (entry.NativeFullName)); + assemblies.Add (temp); + using (var fileStream = File.Create (temp)) { + entry.Extract (fileStream); + } + } + } + } + return assemblies.ToArray (); + } + + string [] Run (params string [] assemblies) + { + var task = new FilterAssemblies { + BuildEngine = new MockBuildEngine (TestContext.Out), + TargetFrameworkIdentifier = "MonoAndroid", + FallbackReference = "Mono.Android", + InputAssemblies = assemblies.Select (a => new TaskItem (a)).ToArray (), + }; + Assert.IsTrue (task.Execute (), "task.Execute() should have succeeded."); + return task.OutputAssemblies.Select (a => Path.GetFileName (a.ItemSpec)).ToArray (); + } + + [Test] + public async Task CircleImageView () + { + var assemblies = await GetAssembliesFromNuGet ( + "https://www.nuget.org/api/v2/package/Refractored.Controls.CircleImageView/1.0.1", + "lib/MonoAndroid10/"); + var actual = Run (assemblies); + var expected = new [] { "Refractored.Controls.CircleImageView.dll" }; + CollectionAssert.AreEqual (expected, actual); + } + + [Test] + public async Task XamarinForms () + { + var assemblies = await GetAssembliesFromNuGet ( + "https://www.nuget.org/api/v2/package/Xamarin.Forms/3.6.0.220655", + "lib/MonoAndroid90/"); + var actual = Run (assemblies); + var expected = new [] { + "FormsViewGroup.dll", + "Xamarin.Forms.Platform.Android.dll", + "Xamarin.Forms.Platform.dll", + }; + CollectionAssert.AreEqual (expected, actual); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj index 94bb7085cf5..e71eb1f8c31 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj @@ -35,6 +35,7 @@ + @@ -97,6 +98,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs index 6d87137d88e..085f94fbd32 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs @@ -726,6 +726,16 @@ public static class KnownPackages } }, }; + public static Package CircleImageView = new Package { + Id = "Refractored.Controls.CircleImageView", + Version = "1.0.1", + TargetFramework = "MonoAndroid10", + References = { + new BuildItem.Reference ("Refractored.Controls.CircleImageView") { + MetadataValues = "HintPath=..\\packages\\Refractored.Controls.CircleImageView.1.0.1\\lib\\MonoAndroid10\\Refractored.Controls.CircleImageView.dll" + } + }, + }; } } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 3cf0c10f731..a4f9ae530d8 100755 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -506,6 +506,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. @@ -2213,7 +2214,7 @@ because xbuild doesn't support framework reference assemblies. + Condition="'%(_ResolvedUserAssemblies.TargetFrameworkIdentifier)' == 'MonoAndroid' Or '%(_ResolvedUserAssemblies.HasMonoAndroidReference)' == 'True'">