Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,26 @@ public override bool RunTask ()
// ItemSpec for these will be "<jarfile>#<entrypath>
// eg: "obj/myjar.jar#myfile.txt"
var jar_file_path = disk_path.Substring (0, disk_path.Length - (jar_entry_name.Length + 1));

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))
Comment on lines 162 to 163
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Performance — The jar is now opened unconditionally, whereas previously the apk.ContainsEntry check was before File.OpenRead/ZipArchive.Open, allowing duplicates from current-build items to be skipped without touching the jar at all. This is probably fine in practice (OS file cache, rare duplicate path), but worth noting as a trade-off for correctness.

Rule: Place cheap checks before expensive ones

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 ();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Collections.Generic;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Formatting — New files should have #nullable enable at the top and use file-scoped namespaces (namespace Xamarin.Android.Build.Tests;). Several other test fixtures in this directory follow those conventions.

Rule: Nullable reference types / File-scoped namespaces

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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 ⚠️ NullabletempDirectory is declared as non-nullable string but is only assigned in Setup(). Without #nullable enable this compiles, but the intent is unclear and fragile. Declare as string? tempDirectory; (consistent with the nullable pattern for [SetUp]-initialized fields per repo conventions).

Rule: Nullable reference types


[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 ExistingJavaArchiveEntriesAreUpdated ()
{
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", "current");
archive.AssertDoesNotContainEntry (apk, "stale.txt");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Testing — Consider adding a test for the case where multiple jar items target the same apk_path — this exercises the preserved duplicate-handling logic (!wasExistingOutputEntry branch). The current tests only cover the existing-output-entry path.

Rule: Test edge cases

[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<BuildMessageEventArgs> ();

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))
using (var archive = ZipArchive.Create (stream)) {
foreach (var entry in entries) {
archive.AddEntry (entry.name, entry.contents, encoding: Encoding.UTF8);
}
}
}
}
}