From 14546190e9aab2ee4471db49da16bb09fcb0726d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:06:22 +0000 Subject: [PATCH 1/4] Initial plan From bee6f1f58c8e77d44a9a06e531e2e74dd719d954 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:38:01 +0000 Subject: [PATCH 2/4] Fix BuildArchive jar entry retention Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../Tasks/BuildArchive.cs | 1 + .../Tasks/BuildArchiveTests.cs | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs index d1fd2af0f67..14b365cae7a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs @@ -158,6 +158,7 @@ public override bool RunTask () // ItemSpec for these will be "# // eg: "obj/myjar.jar#myfile.txt" var jar_file_path = disk_path.Substring (0, disk_path.Length - (jar_entry_name.Length + 1)); + existingEntries.Remove (apk_path); if (apk.ContainsEntry (apk_path)) { Log.LogDebugMessage ("Failed to add jar entry {0} from {1}: the same file already exists in the apk", jar_entry_name, Path.GetFileName (jar_file_path)); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs new file mode 100644 index 00000000000..4b0cfde236b --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs @@ -0,0 +1,66 @@ +using System.IO; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; +using Xamarin.Android.Tasks; +using Xamarin.Tools.Zip; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + public class BuildArchiveTests + { + 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); + } + + [Test] + public void ExistingJavaArchiveEntriesAreRetained () + { + var apk = Path.Combine (tempDirectory, "app.apk"); + var jar = Path.Combine (tempDirectory, "classes.jar"); + + CreateArchive (apk, ("commonMain/default/manifest", "existing"), ("stale.txt", "stale")); + CreateArchive (jar, ("commonMain/default/manifest", "current")); + + var item = new TaskItem ($"{jar}#commonMain/default/manifest"); + item.SetMetadata ("ArchivePath", "commonMain/default/manifest"); + item.SetMetadata ("JavaArchiveEntry", "commonMain/default/manifest"); + + var task = new BuildArchive { + BuildEngine = new MockBuildEngine (TestContext.Out), + ApkOutputPath = apk, + FilesToAddToArchive = new ITaskItem [] { item }, + }; + + Assert.IsTrue (task.RunTask (), "task should have succeeded"); + + using (var archive = ZipArchive.Open (apk, FileMode.Open)) { + archive.AssertEntryContents (apk, "commonMain/default/manifest", "existing"); + archive.AssertDoesNotContainEntry (apk, "stale.txt"); + } + } + + static void CreateArchive (string path, params (string name, string contents) [] entries) + { + using (var stream = File.Create (path)) + using (var archive = ZipArchive.Create (stream)) { + foreach (var entry in entries) { + archive.AddEntry (entry.name, entry.contents, encoding: Encoding.UTF8); + } + } + } + } +} From 95e7370234f2a6adc116113fd0b8540bc2543951 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:01:39 +0000 Subject: [PATCH 3/4] Update stale BuildArchive jar entries Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../Tasks/BuildArchive.cs | 21 +++++++++++++------ .../Tasks/BuildArchiveTests.cs | 4 ++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs index 14b365cae7a..4a752613c3c 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs @@ -158,17 +158,26 @@ public override bool RunTask () // ItemSpec for these will be "# // eg: "obj/myjar.jar#myfile.txt" var jar_file_path = disk_path.Substring (0, disk_path.Length - (jar_entry_name.Length + 1)); - existingEntries.Remove (apk_path); - - if (apk.ContainsEntry (apk_path)) { - Log.LogDebugMessage ("Failed to add jar entry {0} from {1}: the same file already exists in the apk", jar_entry_name, Path.GetFileName (jar_file_path)); - continue; - } + var wasExistingOutputEntry = existingEntries.Remove (apk_path); using (var stream = File.OpenRead (jar_file_path)) using (var jar = ZipArchive.Open (stream)) { var jar_item = jar.ReadEntry (jar_entry_name); + if (apk.ContainsEntry (apk_path)) { + if (!wasExistingOutputEntry) { + Log.LogDebugMessage ("Failed to add jar entry {0} from {1}: the same file already exists in the apk", jar_entry_name, Path.GetFileName (jar_file_path)); + continue; + } + + if (apk.GetEntry (apk_path).CRC == jar_item.CRC) { + Log.LogDebugMessage ("Skipping {0} from {1} as it is up to date.", jar_entry_name, jar_file_path); + continue; + } + + apk.DeleteEntry (apk_path); + } + byte [] data; var d = MemoryStreamPool.Shared.Rent (); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs index 4b0cfde236b..ceba4889a64 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs @@ -27,7 +27,7 @@ public void TearDown () } [Test] - public void ExistingJavaArchiveEntriesAreRetained () + public void ExistingJavaArchiveEntriesAreUpdated () { var apk = Path.Combine (tempDirectory, "app.apk"); var jar = Path.Combine (tempDirectory, "classes.jar"); @@ -48,7 +48,7 @@ public void ExistingJavaArchiveEntriesAreRetained () Assert.IsTrue (task.RunTask (), "task should have succeeded"); using (var archive = ZipArchive.Open (apk, FileMode.Open)) { - archive.AssertEntryContents (apk, "commonMain/default/manifest", "existing"); + archive.AssertEntryContents (apk, "commonMain/default/manifest", "current"); archive.AssertDoesNotContainEntry (apk, "stale.txt"); } } From 2c430e7de6ff4e4c9b4a01dcc4a31318087aba40 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:07:58 +0000 Subject: [PATCH 4/4] Cover up-to-date BuildArchive jar entries Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../Tasks/BuildArchiveTests.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs index ceba4889a64..2fc98ba96a0 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.Build.Framework; @@ -53,6 +54,35 @@ public void ExistingJavaArchiveEntriesAreUpdated () } } + [Test] + public void ExistingJavaArchiveEntriesAreSkippedWhenUpToDate () + { + var apk = Path.Combine (tempDirectory, "app.apk"); + var jar = Path.Combine (tempDirectory, "classes.jar"); + + CreateArchive (apk, ("commonMain/default/manifest", "current")); + CreateArchive (jar, ("commonMain/default/manifest", "current")); + + var item = new TaskItem ($"{jar}#commonMain/default/manifest"); + item.SetMetadata ("ArchivePath", "commonMain/default/manifest"); + item.SetMetadata ("JavaArchiveEntry", "commonMain/default/manifest"); + var messages = new List (); + + var task = new BuildArchive { + BuildEngine = new MockBuildEngine (TestContext.Out, messages: messages), + ApkOutputPath = apk, + FilesToAddToArchive = new ITaskItem [] { item }, + }; + + Assert.IsTrue (task.RunTask (), "task should have succeeded"); + + Assert.That (messages, Has.Some.Property (nameof (BuildMessageEventArgs.Message)).EqualTo ($"Skipping commonMain/default/manifest from {jar} as it is up to date.")); + + using (var archive = ZipArchive.Open (apk, FileMode.Open)) { + archive.AssertEntryContents (apk, "commonMain/default/manifest", "current"); + } + } + static void CreateArchive (string path, params (string name, string contents) [] entries) { using (var stream = File.Create (path))