From 3b3c5129c153579bdb1572cdecf7b9827cb7d990 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 9 May 2023 16:35:49 -0500 Subject: [PATCH 1/2] [Xamarin.Android.Build.Tasks] skip `proguard.txt` files in class libraries Context: https://github.com/xamarin/AndroidX/pull/729 A customer reported a build error such as: C:\Program Files\dotnet\packs\Microsoft.Android.Sdk.Windows\33.0.46\targets\Microsoft.Android.Sdk.AndroidLibraries.targets(77,5): error XACAAR7014: System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. at System.Text.StringBuilder.ToString() at Xamarin.Android.Tasks.CreateAar.RunTask() at Microsoft.Android.Build.Tasks.AndroidTask.Execute() Somewhere here: https://github.com/xamarin/xamarin-android/blob/c31f3eda63a16e412a39fa61eb0f885be3c5f543/src/Xamarin.Android.Build.Tasks/Tasks/CreateAar.cs#L108-L114 The problem appears to be triggered by: ProguardConfigurationFiles= ~\.nuget\packages\xamarin.androidx.versionedparcelable\1.1.1.15\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.lifecycle.runtime\2.5.1.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.core\1.9.0.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.savedstate\1.2.0.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.recyclerview\1.2.1.7\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.lifecycle.viewmodel\2.5.1.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.lifecycle.viewmodelsavedstate\2.5.1.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.fragment\1.5.3.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.vectordrawable.animated\1.1.0.14\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.transition\1.4.1.8\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.startup.startupruntime\1.1.1.2\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.lifecycle.process\2.5.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.coordinatorlayout\1.2.0.3\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.appcompat\1.5.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.google.android.material\1.7.0\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.window\1.0.0.10\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.navigation.common\2.5.2.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.navigation.ui\2.5.2.1\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ~\.nuget\packages\xamarin.androidx.media\1.6.0.2\buildTransitive\net6.0-android31.0\..\..\proguard\proguard.txt ..\path\to\projectreference\obj\Debug\lp\160\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\155\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\161\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\162\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\156\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\157\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\163\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\164\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\165\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\166\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\167\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\170\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\168\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\169\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\171\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\172\jl\proguard.txt ..\path\to\projectreference\obj\Debug\lp\173\jl\proguard.txt We have a compounding issue: * `.aar` files that contain `proguard.txt` files are added to class libraries * These make it to `YourClassLibrary.aar` * You might have class libraries that reference other class libraries * You end up with N^2 `proguard.txt` files and hit OOM. I believe this problem was partially introduced in c537dd28, but we didn't notice until now. The AndroidX NuGet packages also cause this problem, but in a different way as seen in xamarin/AndroidX#729. A solution here is that `` should simply ignore `proguard.txt` files in class libraries. They are only actually needed in "app" projects where the `` MSBuild task would use them. I could reproduce this in a test using an `.aar` from maven that contains a `proguard.txt`: https://mvnrepository.com/artifact/androidx.core/core/1.10.0 And then just assert the `proguard.txt` contents don't make it to `MyClassLibrary.aar`. --- .../Tasks/ResolveLibraryProjectImports.cs | 4 +++- .../Utilities/AssertionExtensions.cs | 13 +++++++++++++ .../Tests/Xamarin.Android.Build.Tests/XASdkTests.cs | 13 ++++++++++--- .../Xamarin.Android.EmbeddedResource.targets | 1 + 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs index f55838abac1..637667b72b8 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs @@ -44,6 +44,8 @@ public class ResolveLibraryProjectImports : AndroidTask [Required] public bool DesignTimeBuild { get; set; } + public bool AndroidApplication { get; set; } + [Output] public ITaskItem [] Jars { get; set; } @@ -442,7 +444,7 @@ void Extract ( resolvedAssetDirectories.Add (new TaskItem (Path.GetFullPath (assetsDir), new Dictionary { { OriginalFile, aarFullPath }, })); - if (File.Exists (proguardFile)) { + if (AndroidApplication && File.Exists (proguardFile)) { proguardConfigFiles.Add (new TaskItem (Path.GetFullPath (proguardFile), new Dictionary { { OriginalFile, aarFullPath }, })); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/AssertionExtensions.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/AssertionExtensions.cs index 8552a37a22b..a6bc7f20c4b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/AssertionExtensions.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/AssertionExtensions.cs @@ -54,6 +54,19 @@ public static void AssertTargetIsPartiallyBuilt (this BuildOutput output, string Assert.IsTrue (output.IsTargetPartiallyBuilt (target), $"The target {target} should have been partially built."); } + [DebuggerHidden] + public static void AssertEntryEquals (this ZipArchive zip, string zipPath, string archivePath, string expected) + { + zip.AssertContainsEntry (zipPath, archivePath); + + var entry = zip.ReadEntry (archivePath); + using var stream = new MemoryStream (); + entry.Extract (stream); + stream.Position = 0; + using var reader = new StreamReader (stream); + Assert.AreEqual (expected, reader.ReadToEnd ().Trim ()); + } + [DebuggerHidden] public static void AssertContainsEntry (this ZipArchive zip, string zipPath, string archivePath) { diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs index fb071d6623e..1c6a1a1b189 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs @@ -79,9 +79,16 @@ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar, bool useDesig BinaryContent = () => XamarinAndroidApplicationProject.icon_binary_mdpi, }, new AndroidItem.ProguardConfiguration ("proguard.txt") { - TextContent = () => @"-ignorewarnings", + TextContent = () => "-ignorewarnings", }, - } + }, + OtherBuildItems = { + new BuildItem ("AndroidLibrary") { + Update = () => "androidx.core.aar", + WebContent = "https://maven.google.com/androidx/core/core/1.10.0/core-1.10.0.aar", + MetadataValues = "Bind=false", + }, + }, }; libC.OtherBuildItems.Add (new AndroidItem.AndroidAsset ("Assets\\bar\\bar.txt") { BinaryContent = () => Array.Empty (), @@ -97,7 +104,7 @@ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar, bool useDesig FileAssert.Exists (aarPath); using (var aar = ZipHelper.OpenZip (aarPath)) { aar.AssertContainsEntry (aarPath, "assets/bar/bar.txt"); - aar.AssertContainsEntry (aarPath, "proguard.txt"); + aar.AssertEntryEquals (aarPath, "proguard.txt", "-ignorewarnings"); } var libB = new XASdkProject (outputType: "Library") { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.EmbeddedResource.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.EmbeddedResource.targets index a525915edbc..1b629e13c06 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.EmbeddedResource.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.EmbeddedResource.targets @@ -38,6 +38,7 @@ This file is used by all project types, including binding projects. Outputs="$(_AndroidStampDirectory)_ResolveLibraryProjectImports.stamp"> Date: Wed, 10 May 2023 11:39:50 -0500 Subject: [PATCH 2/2] Improve test case --- .../Xamarin.Android.Build.Tests/XASdkTests.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs index 1c6a1a1b189..2691ac568bf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs @@ -79,16 +79,9 @@ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar, bool useDesig BinaryContent = () => XamarinAndroidApplicationProject.icon_binary_mdpi, }, new AndroidItem.ProguardConfiguration ("proguard.txt") { - TextContent = () => "-ignorewarnings", + TextContent = () => "# LibraryC", }, - }, - OtherBuildItems = { - new BuildItem ("AndroidLibrary") { - Update = () => "androidx.core.aar", - WebContent = "https://maven.google.com/androidx/core/core/1.10.0/core-1.10.0.aar", - MetadataValues = "Bind=false", - }, - }, + } }; libC.OtherBuildItems.Add (new AndroidItem.AndroidAsset ("Assets\\bar\\bar.txt") { BinaryContent = () => Array.Empty (), @@ -104,7 +97,7 @@ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar, bool useDesig FileAssert.Exists (aarPath); using (var aar = ZipHelper.OpenZip (aarPath)) { aar.AssertContainsEntry (aarPath, "assets/bar/bar.txt"); - aar.AssertEntryEquals (aarPath, "proguard.txt", "-ignorewarnings"); + aar.AssertEntryEquals (aarPath, "proguard.txt", "# LibraryC"); } var libB = new XASdkProject (outputType: "Library") { @@ -146,7 +139,7 @@ public Foo () TextContent = () => ResourceData.JavaSourceTestExtension, }, new AndroidItem.ProguardConfiguration ("proguard.txt") { - TextContent = () => @"-ignorewarnings", + TextContent = () => "# LibraryB", }, } }; @@ -192,6 +185,8 @@ public Foo () aar.AssertContainsEntry (aarPath, $"libs/{projectJarHash}.jar"); aar.AssertContainsEntry (aarPath, "jni/arm64-v8a/libfoo.so"); aar.AssertContainsEntry (aarPath, "jni/x86/libfoo.so"); + // proguard.txt from Library C should not flow to Library B and "double" + aar.AssertEntryEquals (aarPath, "proguard.txt", "# LibraryB"); } // Check EmbeddedResource files do not exist